JavaRush /Java Blog /Random-IT /Un modo semplice per iniettare dipendenze

Un modo semplice per iniettare dipendenze

Pubblicato nel gruppo Random-IT
L'inserimento delle dipendenze (DI) non è un concetto facile da comprendere e applicarlo ad applicazioni nuove o esistenti crea ancora più confusione. Jess Smith ti mostra come eseguire l'inserimento delle dipendenze senza un contenitore di iniezione nei linguaggi di programmazione C# e Java. Metodo semplice per l'iniezione delle dipendenze - 1In questo articolo ti mostrerò come implementare l'inserimento delle dipendenze (DI) nelle applicazioni .NET e Java. Il concetto di inserimento delle dipendenze attirò per la prima volta l'attenzione degli sviluppatori nel 2000, quando Robert Martin scrisse l'articolo "Design Principles and Patterns" (più tardi conosciuto con l'acronimo SOLID ). La D in SOLID si riferisce alla Dipendenza di Inversione (DOI), che in seguito divenne nota come iniezione di dipendenza. La definizione originale e più comune: l'inversione delle dipendenze è un'inversione del modo in cui una classe base gestisce le dipendenze. L'articolo originale di Martin utilizzava il seguente codice per illustrare la dipendenza di una classe Copyda una classe di livello inferiore WritePrinter:
void Copy()
	{
	 int c;
	 while ((c = ReadKeyboard()) != EOF)
		WritePrinter(c);
	}
Il primo problema ovvio è che se si modifica l'elenco dei parametri o i tipi di un metodo WritePrinter, è necessario implementare gli aggiornamenti ovunque esista una dipendenza da quel metodo. Questo processo aumenta i costi di manutenzione ed è una potenziale fonte di nuovi errori.
Interessato a leggere su Java? Unisciti al gruppo di sviluppatori Java !
Altro problema: la classe Copy non è più una potenziale candidata al riutilizzo. Ad esempio, cosa succede se è necessario inviare i caratteri immessi dalla tastiera a un file anziché a una stampante? Per fare ciò, puoi modificare la classe Copycome segue (sintassi del linguaggio C++):
void Copy(outputDevice dev)
	{
	int c;
	while ((c = ReadKeyboard()) != EOF)
		if (dev == printer)
			WritePrinter(c);
		else
			WriteDisk(c);
	}
Nonostante l’introduzione di una nuova dipendenza WriteDisk, la situazione non migliorò (ma anzi peggiorò) perché venne violato un altro principio: “le entità software, cioè classi, moduli, funzioni, ecc., dovrebbero essere aperte per l’estensione, ma chiuse per modifica." Martin spiega che queste nuove istruzioni condizionali if/else riducono la stabilità e la flessibilità del codice. La soluzione è invertire le dipendenze in modo che i metodi di scrittura e lettura dipendano da Copy. Invece di "eseguire" le dipendenze, queste vengono passate attraverso il costruttore. Il codice modificato è simile al seguente:
class Reader
	{
		public:
		virtual int Read() = 0;
	};
	class Writer
	{
		public:
		virtual void Write(char) = 0;
	};
	void Copy(Reader& r, Writer& w)
	{
		int c;
		while((c=r.Read()) != EOF)
		w.Write(c);
	}
Ora la classe Copypuò essere facilmente riutilizzata con diverse implementazioni di metodi di classe Readere Writer. La classe Copynon ha alcuna informazione sulla struttura interna dei tipi Readere Writer, rendendo possibile il riutilizzo con implementazioni diverse. Ma se tutto questo vi sembra una sorta di sciocchezza, forse i seguenti esempi in Java e C# chiariranno la situazione.

Esempio in Java e C#

Per illustrare la facilità dell'inserimento delle dipendenze senza un contenitore di dipendenze, iniziamo con un semplice esempio che può essere personalizzato per l'uso DIin pochi passaggi. Diciamo di avere una classe HtmlUserPresentationche, quando vengono chiamati i suoi metodi, genera un'interfaccia utente HTML. Ecco un semplice esempio:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Qualsiasi progetto che utilizzi questo codice di classe avrà una dipendenza dalla classe HtmlUserPresentation, con conseguenti problemi di usabilità e manutenibilità descritti sopra. Si suggerisce subito un miglioramento: creare un'interfaccia con le firme di tutti i metodi attualmente disponibili nella classe HtmlUserPresentation. Ecco un esempio di questa interfaccia:
public interface IHtmlUserPresentation {
	String createTable(ArrayList rowVals, String caption);
	String createTableRow(String tableCol);
	// Оставшиеся сигнатуры
}
Dopo aver creato l'interfaccia, modifichiamo la classe HtmlUserPresentationper usarla. Tornando all'istanziazione del type HtmlUserPresentation, ora possiamo utilizzare il tipo di interfaccia invece del tipo base:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
La creazione di un'interfaccia ci consente di utilizzare facilmente altre implementazioni di IHtmlUserPresentation. Ad esempio, se vogliamo testare questo tipo, possiamo facilmente sostituire il tipo base HtmlUserPresentationcon un altro tipo chiamato HtmlUserPresentationTest. Le modifiche apportate finora rendono il codice più semplice da testare, mantenere e scalare, ma non fanno nulla per il riutilizzo poiché tutte le HtmlUserPresentationclassi che utilizzano il tipo sono ancora consapevoli della sua esistenza. Per rimuovere questa dipendenza diretta, puoi passare un tipo di interfaccia IHtmlUserPresentational costruttore (o un elenco di parametri del metodo) della classe o del metodo che lo utilizzerà:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
Il costruttore UploadFileora ha accesso a tutte le funzionalità del tipo IHtmlUserPresentation, ma non sa nulla della struttura interna della classe che implementa questa interfaccia. In questo contesto, l'inserimento del tipo avviene quando viene creata un'istanza della classe UploadFile. Un tipo di interfaccia IHtmlUserPresentationdiventa riutilizzabile passando implementazioni diverse a classi o metodi diversi che richiedono funzionalità diverse.

Conclusione e raccomandazioni per consolidare il materiale

Hai appreso l'inserimento delle dipendenze e che si dice che le classi dipendano direttamente l'una dall'altra quando una di esse ne istanzia un'altra per ottenere l'accesso alle funzionalità del tipo di destinazione. Per disaccoppiare la dipendenza diretta tra i due tipi, dovresti creare un'interfaccia. Un'interfaccia offre a un tipo la possibilità di includere diverse implementazioni, a seconda del contesto della funzionalità richiesta. Passando un tipo di interfaccia a un costruttore o a un metodo di classe, la classe/metodo che necessita della funzionalità non conosce alcun dettaglio sul tipo che implementa l'interfaccia. Per questo motivo, un tipo di interfaccia può essere riutilizzato in classi diverse che richiedono un comportamento simile, ma non identico.
  • Per sperimentare l'inserimento delle dipendenze, osserva il tuo codice da una o più applicazioni e prova a convertire un tipo di base molto utilizzato in un'interfaccia.

  • Modificare le classi che istanziano direttamente questo tipo base per utilizzare questo nuovo tipo di interfaccia e passarlo attraverso il costruttore o l'elenco di parametri del metodo della classe che lo utilizzerà.

  • Crea un'implementazione di test per testare questo tipo di interfaccia. Una volta effettuato il refactoring, il codice DIdiventerà più semplice da implementare e noterai quanto più flessibile diventa la tua applicazione in termini di riutilizzo e manutenibilità.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION