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" I1
et I2
into I3
offre 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 : I3
est équivalent à : I1, I2
, : I1, I2
n'est pas un équivalent de : I3
. Même si elles sont fonctionnellement identiques, une classe qui prend en charge les deux I1
et I2
ne 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 I1
et I2
impliquent 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 something
exé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 foo
prend quelque chose qui implémente I1
et 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 foo
si 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, I1
un 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.