Mixins vs composition dans scala
Dans le monde java (plus précisément si vous n'avez pas d'héritage/mixins multiples), la règle de base est assez simple: "favoriser la composition d'objet sur l'héritage de classe".
J'aimerais savoir si / comment cela est changé si vous considérez également les mixins, en particulier dans scala?
Les mixins sont-ils considérés comme un moyen d'héritage multiple, ou plus de composition de classe?
Y a-t-il aussi une directive "Favor object composition over class composition" (ou l'inverse)?
J'en ai vu pas mal exemples lorsque les gens utilisent (ou abusent) mixins lorsque la composition d'objet pourrait également faire le travail et je ne suis pas toujours sûr que l'on est meilleur. Il me semble que vous pouvez réaliser des choses assez similaires avec eux, mais il y a aussi quelques différences, quelques exemples:
- Visibilité - avec mixins tout devient une partie de l'api publique, ce qui n'est pas le cas avec la composition.
- verbosité-dans la plupart des cas, les mixins sont moins verbeux et un peu plus faciles à utiliser, mais ce n'est pas toujours le cas (par exemple, si vous utilisez également des types self dans des hiérarchies complexes)
Je sais que la réponse courte est "ça dépend", mais il y a probablement une situation typique quand ceci ou cela est meilleur.
Quelques exemples de lignes directrices que je pourrais trouver jusqu'à présent (en supposant que J'ai deux traits A et B et A veut utiliser certaines méthodes de B):
- Si vous voulez étendre L'API de A avec les méthodes de B alors mixins, sinon composition. Mais cela n'aide pas si la classe/instance que je suis la création ne fait pas partie d'une API publique.
- Si vous voulez utiliser des modèles qui ont besoin de mixins (par exemple modèle de Trait empilable ) alors c'est une décision facile.
- Si vous avez des dépendances circulaires, les mixins avec les types self peuvent vous aider. (J'essaie d'éviter cette situation, mais il n'est pas toujours facile)
- Si vous voulez des décisions dynamiques, d'exécution comment faire la composition puis la composition de l'objet.
Dans de nombreux cas, les mixins semblent être plus faciles (et / ou moins verbeux), mais je suis sûr qu'ils ont aussi quelques pièges, comme la "classe Dieu" et d'autres décrits dans deux articles artima: Partie 1, partie 2 (BTW il me semble que la plupart des autres problèmes ne sont pas pertinents / pas si sérieux pour scala).
Avez-vous plus de conseils comme ceux-ci?
2 réponses
Beaucoup de problèmes que les gens ont avec les mixages peuvent être évités dans Scala si vous ne mélangez que des traits abstraits dans vos définitions de classe, puis mélangez les traits concrets correspondants au moment de l'instanciation de l'objet. Par exemple
trait Locking{
// abstract locking trait, many possible definitions
protected def lock(body: =>A):A
}
class MyService{
this:Locking =>
}
//For this time, we'll use a java.util.concurrent lock
val myService:MyService = new MyService with JDK15Locking
Cette construction a plusieurs choses à recommander. Tout d'abord, il empêche qu'il y ait une explosion de classes car différentes combinaisons de fonctionnalités de trait sont nécessaires. Deuxièmement, il permet des tests faciles, comme on peut créer et mix - in" do-nothing " traits concrets, semblables à des objets simulés. Enfin, nous avons complètement caché le caractère de verrouillage utilisé, et même ce verrouillage est en cours, des consommateurs de notre service.
Puisque nous avons dépassé la plupart des inconvénients revendiqués des mixages, nous avons toujours un compromis entre le mélange et la composition. Pour moi-même, je prends normalement la décision en fonction de savoir si un objet délégué hypothétique serait entièrement encapsulé par l'objet contenant, ou s'il pourrait potentiellement être partagé et avoir un cycle de vie propre. Le verrouillage fournit un bon exemple de délégués entièrement encapsulés. Si votre classe utilise un objet lock pour gérer l'accès simultané à son état interne, ce verrou est entièrement contrôlé par l'objet contenant, et ni lui ni ses opérations ne sont annoncés dans le cadre de l'interface publique de la classe. Pour une fonctionnalité entièrement encapsulée comme celle-ci, je vais avec des mix-ins. Pour quelque chose de partagé, comme une source de données, utilisez composition.
Autres différences que vous n'avez pas mentionnées:
- Les classes de traits n'ont pas d'existence indépendante:
Si vous trouvez qu'un trait particulier est utilisé le plus souvent en tant que parent d'autres classes, de sorte que les classes enfants se comportent comme le trait parent, envisagez de définir le trait comme une classe à la place, pour rendre cette relation logique plus claire.
(Nous avons dit se comporte comme plutôt que est un, parce que le premier est la définition plus précise de l'héritage, basée sur le principe de Substitution de Liskov - voir [Martin2003], par exemple.)
[Martin2003]: Robert C. Martin, Agile de Développement Logiciel: Principes, Modèles et Pratiques, Prentice-Hall, 2003
- les mixins (
trait
) n'ont aucun paramètre constructeur.
D'où le Conseil , toujours de la programmation Scala :
Évitez les champs concrets dans les traits qui ne peuvent pas être initialisé aux valeurs par défaut appropriées.
Utilisez plutôt des champs abstraits ou convertissez le trait en une classe avec un constructeur .
Bien sûr, les traits sans état n'ont aucun problème avec l'initialisation.C'est un principe général de bonne conception orientée objet qu'une instance doit toujours être dans un état valide à partir du moment où le processus de construction finitions.
Cette dernière partie, quant à la initiale state d'un objet, a souvent aidé à décider entre classe (et composition de classe) et trait (et mixins) pour un concept donné.