dichiarazione di non responsabilità : non ho verificato che il codice di questa risposta sia effettivamente compilato: è solo lì per dimostrare un punto.
Credo che il peggior anti-pattern sia Cargo Cult Programming - seguire ciecamente i modelli di design. Affermo che è molto peggio di qualsiasi altro anti-pattern, perché mentre l'altro anti-pattern indica almeno un processo di pensiero (errato) dall'applicatore, Cargo Cult Programming chiude la mente in modo che non si interrompa nell'atto di applicando modelli di progettazione inutili.
Nel nostro caso, sembra che il modello Dependency Inversion sia oggetto di culto del carico. L'utilizzo di DI per ogni classe (salvo per le classi di soli dati) è impossibile, poiché a un certo punto è necessario costruire classi concrete. Per lo meno, alcuni metodi statici e tutte le classi di fabbrica dovrebbero essere esenti da questa regola.
Questa esenzione renderà il compito possibile, ma ancora inutilmente difficile. La chiave che sta dietro all'utilizzo di Dependency Injection è capire che è necessario disaccoppiare moduli (o componenti), non classi. Le classi dello stesso modulo dovrebbero poter utilizzare altre classi concrete dello stesso modulo. Ovviamente - decidere dove vanno i confini tra i componenti - quali classi appartengono a ciascun componente - richiede il pensiero, qualcosa che è proibito nelle sette del carico.
Un'altra cosa importante è che alcune cose sono troppo piccole per poter essere usate solo per interfaccia, e alcune cose sono troppo alte per le sole interfacce di utilizzo.
Il tuo esempio di Math.Floor
è ed esempio di "troppo piccolo per can-be-used-by-interface-only". L'inversione della dipendenza qui richiede qualcosa come:
interface IMath{
public double Floor(double d);
}
public void Foo(IMath math){
// ...
double roundedAge=math.Floor(exactAge);
// ...
}
Questo ci permette di fare cose come:
- Chiama
Foo
con un'altra implementazione di IMath
che implementa Floor
in modo diverso
- Mock
IMath
nei test unitari - chiama Foo
con un oggetto IMath
che non fa realmente la pavimentazione, fa solo finta che lo faccia.
Devi essere molto profondo nel culto del carico per ignorare il fatto che questi due benefici sono completamente inutili qui. È difficile immaginare un metodo che faccia Floor
meglio di System.Math.Floor
, e qualsiasi tentativo di deriderlo - a meno che non funzioni solo per casi specifici - sarà più complicato e prune di errori che semplicemente facendo il dannato pavimento.
Confronta con i costi:
- Dovendo inviare un'implementazione
IMath
a Foo
.
-
L'implementazione di
Foo
(utilizzando un oggetto concreto IMath
) è esposta dalla sua interfaccia (richiede un oggetto IMath
come argomento).
- Anche tutti i metodi che utilizzano
Foo
avranno questi costi, dal momento che non può utilizzare direttamente un% di% di co_de concreto.
ed è facile vedere che non vale la pena in questo caso.
Un esempio di "troppo alto per le sole interfacce di utilizzo" è il tuo metodo IMath
, che deve effettivamente creare oggetti concreti a cui non può essere iniettato.
Il tuo esempio di Main
è in realtà un buon esempio per qualcosa che può possibilmente beneficiare di Dependency Inversion:
- È possibile che tu voglia fornire un'implementazione diversa di
StreamReader
, ad esempio una che legge e decrittografa un flusso crittografato.
- È possibile che tu voglia simulare
TextReader
in un test di unità, quindi non avrai bisogno di un file effettivo per esistere in un percorso specifico quando esegui il tuo test di unità e puoi invece fornire il testo che sarebbe leggere direttamente nel test dell'unità.
Ancora: a un certo punto è necessario creare un oggetto concreto. Qui dovremmo trovare un luogo "troppo alto per le sole interfacce di utilizzo", in cui possiamo creare TextReader
o un oggetto factory che può creare TextReader
e iniettarlo fino a quando non viene utilizzato:
interface ITextReaderFactory{
TextReader CreateTextReader(string path);
}
public void Bar(ITextReaderFactory textReaderFactory){
// ...
using(TextReader reader=textReaderFactory.CreateTextReader(path)){
// do things
}
// ...
}
class StreamReaderFactory : ITextReaderFactory{
public TextReader CreateTextReader(string path){
return new StreamReader(path);
}
}
public static void Main(string[] args){
Bar(new StreamReaderFactory());
}