Tout ce qui implémente I3 devra implémenter I1 et I2, et les objets de différentes classes qui héritent d'I3 peuvent alors être utilisés de manière interchangeable (voir ci-dessous), est-il donc correct d'appeler I3 vide? Si tel est le cas, quelle serait la meilleure façon d’archiver cela?
"Bundling" I1et I2into I3offre un bon raccourci (?), Ou une sorte de sucre de syntaxe pour l'endroit où vous souhaitez que les deux soient implémentés.
Le problème avec cette approche est que vous ne pouvez pas empêcher d'autres développeurs d'implémenter explicitement I1 et I2 côte à côte, mais cela ne fonctionne pas dans les deux sens - alors qu'il : I3est équivalent à : I1, I2, : I1, I2n'est pas un équivalent de : I3. Même si elles sont fonctionnellement identiques, une classe qui prend en charge les deux I1et I2ne sera pas détectée comme prenant en charge I3.
Cela signifie que vous offrez deux façons d'accomplir la même chose - "manuelle" et "douce" (avec votre sucre de syntaxe). Et cela peut avoir un mauvais impact sur la cohérence du code. Offrir 2 façons différentes d'accomplir la même chose ici, là et ailleurs entraîne déjà 8 combinaisons possibles :)
void foo() {
I1 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function(); // we can't do this without casting first
}
OK, tu ne peux pas. Mais c'est peut-être en fait un bon signe?
Si I1et I2impliquent des responsabilités différentes (sinon il ne serait pas nécessaire de les séparer en premier lieu), alors peut foo()- être qu'il est faux d'essayer de faire somethingexécuter deux responsabilités différentes en une seule fois?
En d'autres termes, il se pourrait que foo()lui - même viole le principe de la responsabilité unique, et le fait qu'il nécessite des vérifications de type de coulée et d'exécution pour le retirer est un drapeau rouge nous alarmant à ce sujet.
ÉDITER:
Si vous insistez pour vous assurer que fooprend quelque chose qui implémente I1et I2, vous pouvez les passer en tant que paramètres distincts:
void foo(I1 i1, I2 i2)
Et ils pourraient être le même objet:
foo(something, something);
Qu'importe foosi c'est la même instance ou non?
Mais si cela se soucie (par exemple parce qu'ils ne sont pas apatrides), ou si vous pensez simplement que cela semble moche, on pourrait utiliser des génériques à la place:
void Foo<T>(T something) where T: I1, I2
Désormais, les contraintes génériques prennent soin de l'effet recherché sans polluer le monde extérieur avec quoi que ce soit. Il s'agit d'un langage C # idiomatique, basé sur des vérifications à la compilation, et il semble globalement plus correct.
Je vois I3 : I2, I1un peu comme un effort pour émuler des types d'union dans un langage qui ne le prend pas en charge (C # ou Java non, alors que les types d'union existent dans les langages fonctionnels).
Essayer d'imiter une construction qui n'est pas vraiment prise en charge par le langage est souvent maladroit. De même, en C #, vous pouvez utiliser des méthodes d'extension et des interfaces si vous souhaitez émuler des mixins . Possible? Oui. Bonne idée? Je ne suis pas sûr.