Anything that implements I3 will need to implement I1 and I2, and objects from different classes that inherit I3 can then be used interchangeably (see below), so is it right to call I3 empty? If so what would be a better way to architecture this?
"Raggruppare" I1
e I2
in I3
offre una bella scorciatoia (?), o una sorta di sintassi per lo zucchero dove vuoi che siano entrambi implementati.
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 - mentre : I3
è equivalente a : I1, I2
, : I1, I2
non è un equivalente di : I3
. Anche se sono funzionalmente identici, una classe che supporta sia I1
che I2
non verrà rilevata come supporto I3
.
Questo significa che stai offrendo due modi per realizzare la stessa cosa: "manuale" e "dolce" (con lo zucchero della sintassi). E può avere un impatto negativo sulla coerenza del codice. Offrire 2 diversi modi per realizzare la stessa cosa qui, lì, e in un altro luogo risulta già in 8 combinazioni possibili:)
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 è davvero un buon segno?
Se I1
e I2
implicano responsabilità diverse (altrimenti non sarebbe necessario separarli in primo luogo), allora forse foo()
è sbagliato per provare a fare something
eseguire due diverse responsabilità in una partire?
In altre parole, potrebbe essere che foo()
in sé viola il principio di Single Responsibility, e il fatto che richieda il casting e i controlli del tipo di esecuzione per rimuoverlo è una bandiera rossa che ci allarma.
Modifica
Se hai insistito per garantire che foo
richieda qualcosa che implementa I1
e I2
, potresti passarli come parametri separati:
void foo(I1 i1, I2 i2)
E potrebbero essere lo stesso oggetto:
foo(something, something);
Che cosa importa foo
se sono la stessa istanza o no?
Ma se si preoccupa (ad esempio perché non sono apolidi), o pensi solo che questo sembra brutto, si potrebbe usare invece generici:
void Foo<T>(T something) where T: I1, I2
Ora i vincoli generici si prendono cura dell'effetto desiderato mentre non inquinano il mondo esterno con nulla. Questo è C # idiomatico, basato su verifiche in fase di compilazione, e sembra più giusto nel complesso.
Vedo I3 : I2, I1
in qualche modo come uno sforzo per emulare tipi di unione in una lingua che non lo supporta della scatola (C # o Java no, mentre i tipi di unione esistono nei linguaggi funzionali).
Cercare di emulare un costrutto che non è realmente supportato dalla lingua è spesso clunky. Analogamente, in C # puoi utilizzare i metodi di estensione e le interfacce se desideri emulare mixins . Possibile? Sì. Buona idea? Non sono sicuro.