Si vous écrivez du code qui utilise beaucoup de belles structures de données immuables, les classes de cas semblent être une aubaine, vous offrant tout ce qui suit gratuitement avec un seul mot-clé:
- Tout est immuable par défaut
- Getters définis automatiquement
- Implémentation décente de toString ()
- Conforme equals () et hashCode ()
- Objet compagnon avec méthode unapply () pour la correspondance
Mais quels sont les inconvénients de la définition d'une structure de données immuable en tant que classe de cas?
Quelles restrictions impose-t-il à la classe ou à ses clients?
Y a-t-il des situations où vous devriez préférer une classe sans cas?
scala
case-class
Graham Lea
la source
la source
Réponses:
Un gros inconvénient: une classe de cas ne peut pas étendre une classe de cas. Voilà la restriction.
Autres avantages que vous avez manqués, listés par souci d'exhaustivité: sérialisation / désérialisation conforme, pas besoin d'utiliser "nouveau" mot-clé pour créer.
Je préfère les classes sans casse pour les objets avec un état mutable, un état privé ou aucun état (par exemple, la plupart des composants singleton). Classes de cas pour à peu près tout le reste.
la source
D'abord les bons morceaux:
Tout est immuable par défaut
Oui, et peut même être remplacé (en utilisant
var
) si vous en avez besoinGetters définis automatiquement
Possible dans n'importe quelle classe en préfixant les paramètres avec
val
Mise en
toString()
œuvre décenteOui, très utile, mais faisable à la main sur n'importe quel cours si nécessaire
Conforme
equals()
ethashCode()
Combiné à une correspondance de motifs facile, c'est la principale raison pour laquelle les gens utilisent des classes de cas
Objet compagnon avec
unapply()
méthode de correspondanceÉgalement possible de faire à la main sur n'importe quelle classe en utilisant des extracteurs
Cette liste devrait également inclure la méthode de copie ultra-puissante, l'une des meilleures choses à venir dans Scala 2.8
Ensuite, il n'y a qu'une poignée de restrictions réelles avec des classes de cas:
Vous ne pouvez pas définir
apply
dans l'objet compagnon en utilisant la même signature que la méthode générée par le compilateurEn pratique cependant, c'est rarement un problème. Changer le comportement de la méthode apply générée est garanti pour surprendre les utilisateurs et devrait être fortement déconseillé, la seule justification pour le faire est de valider les paramètres d'entrée - une tâche mieux effectuée dans le corps du constructeur principal (qui rend également la validation disponible lors de l'utilisation
copy
)Vous ne pouvez pas sous-classer
C'est vrai, bien qu'il soit toujours possible pour une classe de cas d'être elle-même un descendant. Un modèle courant consiste à construire une hiérarchie de classes de traits, en utilisant des classes de cas comme nœuds feuilles de l'arbre.
Il convient également de noter le
sealed
modificateur. Toute sous-classe d'un trait avec ce modificateur doit être déclarée dans le même fichier. Lors de la mise en correspondance de modèles avec des instances du trait, le compilateur peut alors vous avertir si vous n'avez pas vérifié toutes les sous-classes concrètes possibles. Lorsqu'il est combiné avec des classes de cas, cela peut vous offrir un niveau de confiance très élevé dans votre code s'il se compile sans avertissement.En tant que sous-classe de Product, les classes de cas ne peuvent pas avoir plus de 22 paramètres
Pas de vraie solution de contournement, sauf pour arrêter d'abuser des classes avec autant de paramètres :)
Aussi...
Une autre restriction parfois notée est que Scala ne supporte pas (actuellement) les paramètres paresseux (comme
lazy val
s, mais en tant que paramètres). La solution de contournement consiste à utiliser un paramètre par nom et à l'affecter à une valeur paresseuse dans le constructeur. Malheureusement, les paramètres par nom ne se mélangent pas avec la correspondance de modèles, ce qui empêche la technique d'être utilisée avec des classes de cas car elle rompt l'extracteur généré par le compilateur.Ceci est pertinent si vous souhaitez implémenter des structures de données paresseuses hautement fonctionnelles, et sera, espérons-le, résolu avec l'ajout de paramètres paresseux dans une future version de Scala.
la source
Je pense que le principe TDD s'applique ici: ne pas sur-concevoir. Lorsque vous déclarez que quelque chose est a
case class
, vous déclarez de nombreuses fonctionnalités. Cela diminuera la flexibilité dont vous disposez pour changer de classe à l'avenir.Par exemple, a
case class
a uneequals
méthode sur les paramètres du constructeur. Vous ne vous souciez peut-être pas de cela lorsque vous écrivez votre classe pour la première fois, mais ce dernier peut décider que vous voulez que l'égalité ignore certains de ces paramètres, ou fasse quelque chose d'un peu différent. Cependant, le code client peut être écrit dans l'intervalle de temps qui dépend de l'case class
égalité.la source
Martin Odersky nous donne un bon point de départ dans son cours Principes de programmation fonctionnelle en Scala (Lecture 4.6 - Pattern Matching) que nous pourrions utiliser lorsque nous devons choisir entre la classe et la classe de cas. Le chapitre 7 de Scala By Example contient le même exemple.
De plus, l'ajout d'une nouvelle classe Prod n'entraîne aucune modification du code existant:
En revanche, l'ajout d'une nouvelle méthode nécessite la modification de toutes les classes existantes.
Le même problème résolu avec les classes de cas.
L'ajout d'une nouvelle méthode est un changement local.
L'ajout d'une nouvelle classe Prod nécessite potentiellement de modifier toutes les correspondances de modèles.
Transcription de la vidéolecture 4.6 Pattern Matching
N'oubliez pas: nous devons utiliser cela comme un point de départ et non comme les seuls critères.
la source
Je cite ce de
Scala cookbook
par leAlvin Alexander
chapitre 6:objects
.C'est l'une des nombreuses choses que j'ai trouvées intéressantes dans ce livre.
Pour fournir plusieurs constructeurs pour une classe case, il est important de savoir ce que fait réellement la déclaration de classe case.
Si vous regardez le code généré par le compilateur Scala pour l'exemple de classe de cas, vous verrez qu'il crée deux fichiers de sortie, Person $ .class et Person.class. Si vous désassemblez Person $ .class avec la commande javap, vous verrez qu'elle contient une méthode apply, ainsi que bien d'autres:
Vous pouvez également désassembler Person.class pour voir ce qu'il contient. Pour une classe simple comme celle-ci, elle contient 20 méthodes supplémentaires; ce ballonnement caché est l'une des raisons pour lesquelles certains développeurs n'aiment pas les classes de cas.
la source