Mettre la méthode en trait ou, dans le cas de la classe?
Il y a deux façons de définir une méthode de deux classes différentes hériter de la même caractéristique dans le Scala.
sealed trait Z { def minus: String }
case class A() extends Z { def minus = "a" }
case class B() extends Z { def minus = "b" }
L'alternative est la suivante:
sealed trait Z { def minus: String = this match {
case A() => "a"
case B() => "b"
}
case class A() extends Z
case class B() extends Z
La première méthode répète le nom de la méthode, alors que la deuxième méthode répète le nom de la classe.
Je pense que la première méthode est la meilleure à utiliser, car les codes sont séparés. Cependant, je me suis trouvé souvent à l'aide de la seconde compliqué de méthodes, de sorte que l'ajout d'arguments supplémentaires peut être fait très facilement, par exemple comme ceci:
sealed trait Z {
def minus(word: Boolean = false): String = this match {
case A() => if(word) "ant" else "a"
case B() => if(word) "boat" else "b"
}
case class A() extends Z
case class B() extends Z
Quelles sont les autres différences entre ces pratiques? Existe-il des bugs qui m'attendait si je choisis la deuxième approche?
EDIT:
J'ai été cité le ouvert/fermé principe, mais parfois, j'ai besoin de modifier non seulement la sortie de l'fonctions selon de nouvelles classes de cas, mais aussi l'entrée en raison de code refactoring. Est-il un meilleur modèle que le premier? Si je veux ajouter de la précédente fonctionnalités mentionnées dans le premier exemple, cela donnerait le laid code où l'entrée est répété:
sealed trait Z { def minus(word: Boolean): String ; def minus = minus(false) }
case class A() extends Z { def minus(word: Boolean) = if(word) "ant" else "a" }
case class B() extends Z { def minus(word: Boolean) = if(word) "boat" else "b" }
- Permettez-moi de spéculer que tout ajout non négligeable de la méthode de la classe de cas est malsain de OO point de vue. Cas instance de classe expose l'ensemble de ses entrailles, et les bons objets ne le sont pas. Il n'est donc pas un "vrai" objet, mais primitive semblables, comme string ou un tuple.
Vous devez vous connecter pour publier un commentaire.
Noter qu'avec
Dotty
(fondation deScala 3
), vous avez la possibilité d'utiliser trait paramètres (tout comme les classes de paramètres), ce qui simplifie les choses d'assez nombreux dans ce cas:trait Z(val x: String) { def minus: String = x }
pour y accéder:A().x
. Vous pouvez également ajouter plusieurs paramètres à la caractéristique et de choisir dans le "mot" condition:trait Z(x: String, y: String) { def minus(word: Boolean = false): String = if (word) y else x }
-case class A() extends Z("a", "c")
-A().minus(true)
qui renvoie"c"
.J'opte pour la première.
Pourquoi ? Simplement pour garder Ouvert/Fermé Principe.
En effet, si vous souhaitez ajouter une autre sous-classe, disons
case class C
, vous aurez à modifier supertrait/super-classe pour insérer la nouvelle condition... laidVotre scénario est similaire à Java avec modèle et de la stratégie de modèle contre conditionnelle.
Mise à JOUR:
Dans votre dernier scénario, vous ne pouvez pas éviter la "duplication" de l'entrée. En effet, le type de paramètre à Scala n'est pas en résultant.
Il encore mieux d'avoir cohérente des méthodes de fusion de l'ensemble à l'intérieur d'une méthode présentant autant de paramètres que la méthode de l'union attend.
Imaginez dix conditions dans votre supertrait méthode. Que faire si vous changez, par inadvertance, le comportement de l'un de chaque? Chaque changement serait risqué, et supertrait les tests unitaires doivent toujours s'exécuter à chaque fois que vous le modifier ...
En outre de changer, par inadvertance, un paramètre d'entrée (pas un COMPORTEMENT) n'est pas "dangereux" à tous. Pourquoi? parce que le compilateur vous dira qu'un paramètre/type de paramètre n'est pas pertinent, pas plus.
Et si vous voulez le changer, et faire de même pour toutes les sous-classes...demandez à votre IDE, il aime refactoring ce genre de choses en un seul clic.
Que ce lien explique:
Pourquoi ouvert-fermé le principe des questions:
Mise à JOUR 2:
Voici un exemple d'éviter la duplication des entrées correspondant à votre attente:
Grâce inférence de type 🙂
B
le comportement n'est pas nécessaire à tous lesemphasizeA
. Je peux donc revenir à cette question à votre premier commentaire: ce qui est mieux? Cohérente fonction, rapidement compréhensible et court, ou de l'imposition de tous les comportements de la sous-classe pour s'adapter à une signature spécifique, même s'ils n'en ont pas besoin ...Personnellement, je préfère rester à l'écart de la deuxième approche. Chaque fois que vous ajoutez une nouvelle sous-classe de Z tu dois toucher l'partagés moins de la méthode, pourrait mettre en péril le comportement lié à la implémentations existantes. Avec la première approche de l'ajout d'une nouvelle sous-classe n'a pas d'effet secondaire possible sur les structures existantes. Il y a peut-être un peu de l'Ouvrir/Fermé le Principe ici et votre deuxième approche pourrait violer.
Ouvert/Fermé principe peut être violé avec les deux approches. Ils sont orthogonaux les uns aux autres. Le premier permet d'ajouter facilement de nouveaux type et mettre en œuvre des méthodes requises, il rompt Ouvert/Fermé principe si vous devez ajouter une nouvelle méthode dans la hiérarchie ou à refactoriser signatures de méthode, au point qu'il se casse, code client. Il est, après tout, pourquoi défaut méthodes ont été ajoutées à Java8 interfaces afin que les anciennes API peut être étendu sans nécessiter de code client à s'adapter.
Cette approche est typique pour la programmation orientée objet.
La seconde approche est plus typique pour les FP. Dans ce cas, il est facile d'ajouter des méthodes, mais il est difficile d'ajouter un nouveau type (il se casse O/C ici). C'est une bonne approche pour fermé les hiérarchies, exemple typique sont des Types de Données Algébriques (ADT). Protocole standardisé qui n'est pas destiné à être étendu par les clients peut être un candidat.
Langues lutte pour permettre à l'API de conception qui aurait à la fois des avantages - facile à ajouter des types ainsi que l'ajout de méthodes. Ce problème est appelé Problème de l'Expression. Scala offre Typeclass modèle pour résoudre ce problème qui permet d'ajouter des fonctionnalités à des types existants en ad-hoc et de manière sélective.
Qui est mieux dépend de votre cas d'utilisation.