Les traits sont une autre façon de composer. Considérez-les comme un moyen de composer toutes les parties d'une classe au moment de la compilation (ou du temps de compilation JIT), en assemblant les implémentations concrètes des parties dont vous aurez besoin.
Fondamentalement, vous souhaitez utiliser des traits lorsque vous vous retrouvez à créer des classes avec différentes combinaisons de fonctionnalités. Cette situation se présente le plus souvent pour les personnes qui écrivent des bibliothèques flexibles pour les autres à consommer. Par exemple, voici la déclaration d'une classe de test unitaire que j'ai écrite récemment en utilisant ScalaTest :
class TestMyClass
extends WordSpecLike
with Matchers
with MyCustomTrait
with BeforeAndAfterAll
with BeforeAndAfterEach
with ScalaFutures
Les frameworks de tests unitaires ont une tonne d'options de configuration différentes, et chaque équipe a des préférences différentes sur la façon dont elle veut faire les choses. En plaçant les options dans des traits (qui sont mélangés dans with
Scala), ScalaTest peut offrir toutes ces options sans avoir à créer des noms de classe comme WordSpecLikeWithMatchersAndFutures
, ou une tonne d'indicateurs booléens d'exécution comme WordSpecLike(enableFutures, enableMatchers, ...)
. Cela facilite le respect du principe d'ouverture / fermeture . Vous pouvez ajouter de nouvelles fonctionnalités et de nouvelles combinaisons de fonctionnalités, simplement en ajoutant un nouveau trait. Cela facilite également le respect du principe de ségrégation d'interface , car vous pouvez facilement mettre des fonctions qui ne sont pas universellement nécessaires dans un trait.
Les traits sont également un bon moyen de mettre du code commun dans plusieurs classes qui n'ont pas de sens pour partager une hiérarchie d'héritage. L'hérédité est une relation très étroitement liée, et vous ne devriez pas payer ce coût si vous pouvez l'aider. Les traits sont une relation beaucoup plus lâche couplée. Dans mon exemple ci-dessus, j'avais l'habitude MyCustomTrait
de partager facilement une implémentation de base de données fictive entre plusieurs classes de test autrement non liées.
L'injection de dépendances atteint plusieurs des mêmes objectifs, mais au moment de l'exécution en fonction des entrées de l'utilisateur plutôt qu'au moment de la compilation en fonction des entrées du programmeur. Les traits sont également destinés davantage aux dépendances qui font sémantiquement partie de la même classe. Vous êtes en quelque sorte assembler les parties d'une classe plutôt que de faire des appels à d'autres classes avec d'autres responsabilités.
Les frameworks d' injection de dépendances atteignent plusieurs des mêmes objectifs au moment de la compilation sur la base des entrées du programmeur, mais constituent en grande partie une solution de contournement pour les langages de programmation sans prise en charge appropriée des traits. Les traits apportent ces dépendances dans le domaine du vérificateur de type du compilateur, avec une syntaxe plus propre, avec un processus de construction plus simple, qui établit une distinction plus claire entre les dépendances à la compilation et à l'exécution.