Tutto ciò che implementa I3 dovrà implementare I1 e I2, e gli oggetti di diverse classi che ereditano I3 possono quindi essere usati in modo intercambiabile (vedi sotto), quindi è giusto chiamare I3 vuoto? In caso affermativo quale sarebbe un modo migliore per l'architettura questo?
"Bundling" I1
e I2
into I3
offre una scorciatoia (?) O una sorta di zucchero di sintassi per ovunque tu voglia implementare entrambi.
Il problema con questo approccio è che non puoi impedire ad altri sviluppatori di implementare esplicitamente I1
e I2
fianco a fianco, ma non funziona in entrambi i modi - anche se : I3
è equivalente a : I1, I2
, : I1, I2
non è equivalente a : I3
. Anche se funzionalmente identici, una classe che supporta entrambi I1
e I2
non verrà rilevata come supporto I3
.
Ciò significa che stai offrendo due modi per ottenere lo stesso risultato: "manuale" e "dolce" (con lo zucchero della sintassi). E può avere un impatto negativo sulla coerenza del codice. Offrendo 2 modi diversi per ottenere la stessa cosa qui, lì e in un altro posto si ottengono già 8 possibili combinazioni :)
void foo() {
I1 something = new A();
something = new B();
something.SomeI1Function();
something.SomeI2Function(); // we can't do this without casting first
}
OK, non puoi. Ma forse è in realtà un buon segno?
Se I1
e I2
implicare responsabilità diverse (altrimenti non sarebbe necessario separarle in primo luogo), allora forse foo()
è sbagliato provare a far something
eseguire due diverse responsabilità in una volta sola?
In altre parole, potrebbe essere esso foo()
stesso a violare il principio della responsabilità singola e il fatto che richieda controlli di tipo casting e runtime per eseguirlo è una bandiera rossa che ci allarma.
MODIFICARE:
Se hai insistito per assicurarti che foo
prendesse qualcosa che implementasse I1
e I2
, potresti passarli come parametri separati:
void foo(I1 i1, I2 i2)
E potrebbero essere lo stesso oggetto:
foo(something, something);
Cosa foo
importa se sono la stessa istanza o no?
Ma se importa (ad es. Perché non sono apolidi), o pensi semplicemente che sembri brutto, potresti invece usare i generici:
void Foo<T>(T something) where T: I1, I2
Ora i vincoli generici si occupano dell'effetto desiderato senza inquinare il mondo esterno con nulla. Questo è C # idiomatico, basato su controlli in fase di compilazione, e nel complesso sembra più giusto.
Vedo in I3 : I2, I1
qualche modo uno sforzo per emulare i tipi di unione in un linguaggio che non lo supporta immediatamente (C # o Java no, mentre i tipi di unione esistono in linguaggi funzionali).
Cercare di emulare un costrutto che non è realmente supportato dalla lingua è spesso ingombrante. Allo stesso modo, in C # è possibile utilizzare metodi di estensione e interfacce se si desidera emulare i mixin . Possibile? Sì. Buona idea? Non ne sono sicuro.