JavaRush /Blog Java /Random-PL /Łatwy sposób na wstrzykiwanie zależności

Łatwy sposób na wstrzykiwanie zależności

Opublikowano w grupie Random-PL
Wstrzykiwanie zależności (DI) nie jest koncepcją łatwą do zrozumienia, a zastosowanie jej do nowych lub istniejących aplikacji jest jeszcze bardziej zagmatwane. Jess Smith pokazuje, jak wykonać zastrzyk zależności bez kontenera wstrzykiwania w językach programowania C# i Java. Prosty sposób wstrzykiwania zależności - 1W tym artykule pokażę, jak zaimplementować wstrzykiwanie zależności (DI) w aplikacjach .NET i Java. Koncepcja wstrzykiwania zależności po raz pierwszy zwróciła uwagę programistów w 2000 roku, kiedy Robert Martin napisał artykuł „Zasady i wzorce projektowe” (później znany pod akronimem SOLID ). Litera D w SOLID odnosi się do zależności inwersji (DOI), która później stała się znana jako wstrzykiwanie zależności. Oryginalna i najczęstsza definicja: inwersja zależności to odwrócenie sposobu, w jaki klasa bazowa zarządza zależnościami. W oryginalnym artykule Martina wykorzystano następujący kod, aby zilustrować zależność klasy Copyod klasy niższego poziomu WritePrinter:
void Copy()
	{
	 int c;
	 while ((c = ReadKeyboard()) != EOF)
		WritePrinter(c);
	}
Pierwszym oczywistym problemem jest to, że jeśli zmienisz listę parametrów lub typy metody WritePrinter, musisz zaimplementować aktualizacje wszędzie tam, gdzie istnieje zależność od tej metody. Proces ten zwiększa koszty utrzymania i jest potencjalnym źródłem nowych błędów.
Chcesz przeczytać o Javie? Dołącz do grupy programistów Java !
Kolejny problem: klasa Copy nie jest już potencjalnym kandydatem do ponownego użycia. Na przykład, co się stanie, jeśli chcesz przesłać znaki wprowadzone z klawiatury do pliku, a nie do drukarki? Aby to zrobić, możesz zmodyfikować klasę Copyw następujący sposób (składnia języka C++):
void Copy(outputDevice dev)
	{
	int c;
	while ((c = ReadKeyboard()) != EOF)
		if (dev == printer)
			WritePrinter(c);
		else
			WriteDisk(c);
	}
Pomimo wprowadzenia nowej zależności WriteDisksytuacja nie poprawiła się (a raczej pogorszyła), gdyż naruszona została kolejna zasada: „byty programowe, czyli klasy, moduły, funkcje itd. powinny być otwarte na rozbudowę, ale zamknięte na modyfikacja." Martin wyjaśnia, że ​​te nowe instrukcje warunkowe if/else zmniejszają stabilność i elastyczność kodu. Rozwiązaniem jest odwrócenie zależności, tak aby metody zapisu i odczytu zależały od Copy. Zamiast „przepychać” zależności, są one przekazywane przez konstruktor. Zmodyfikowany kod wygląda następująco:
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);
	}
Teraz klasę Copymożna łatwo ponownie wykorzystać z różnymi implementacjami metod klasowych Readeri Writer. Klasa Copynie posiada żadnych informacji o wewnętrznej strukturze typów Readeri Writer, co umożliwia ich ponowne wykorzystanie w różnych implementacjach. Ale jeśli to wszystko wydaje ci się jakimś bełkotem, być może poniższe przykłady w Javie i C# wyjaśnią sytuację.

Przykład w Javie i C#

Aby zilustrować łatwość wstrzykiwania zależności bez kontenera zależności, zacznijmy od prostego przykładu, który można dostosować do użycia DIw zaledwie kilku krokach. Załóżmy, że mamy klasę HtmlUserPresentation, która po wywołaniu jej metod generuje interfejs użytkownika HTML. Oto prosty przykład:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Każdy projekt wykorzystujący ten kod klasy będzie zależny od klasy HtmlUserPresentation, co spowoduje opisane powyżej problemy z użytecznością i konserwacją. Od razu nasuwa się usprawnienie: utworzenie interfejsu z sygnaturami wszystkich metod aktualnie dostępnych w klasie HtmlUserPresentation. Oto przykład tego interfejsu:
public interface IHtmlUserPresentation {
	String createTable(ArrayList rowVals, String caption);
	String createTableRow(String tableCol);
	// Оставшиеся сигнатуры
}
Po utworzeniu interfejsu modyfikujemy klasę tak, HtmlUserPresentationaby z niego korzystała. Wracając do tworzenia instancji typu HtmlUserPresentation, możemy teraz użyć typu interfejsu zamiast typu podstawowego:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Stworzenie interfejsu pozwala nam łatwo korzystać z innych implementacji IHtmlUserPresentation. Przykładowo, jeśli chcemy przetestować ten typ, możemy łatwo zastąpić typ podstawowy HtmlUserPresentationinnym typem o nazwie HtmlUserPresentationTest. Wprowadzone do tej pory zmiany ułatwiają testowanie, konserwację i skalowanie kodu, ale nie powodują ponownego wykorzystania, ponieważ wszystkie HtmlUserPresentationklasy korzystające z tego typu są nadal świadome jego istnienia. Aby usunąć tę bezpośrednią zależność, możesz przekazać typ interfejsu IHtmlUserPresentationdo konstruktora (lub listy parametrów metody) klasy lub metody, która będzie go używać:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
Konstruktor UploadFilema teraz dostęp do wszystkich funkcjonalności typu IHtmlUserPresentation, ale nie wie nic o wewnętrznej strukturze klasy, która implementuje ten interfejs. W tym kontekście wstrzykiwanie typu ma miejsce, gdy tworzona jest instancja klasy UploadFile. Typ interfejsu IHtmlUserPresentationstaje się możliwy do ponownego użycia poprzez przekazanie różnych implementacji do różnych klas lub metod, które wymagają różnych funkcjonalności.

Wnioski i zalecenia dotyczące konsolidacji materiału

Dowiedziałeś się o wstrzykiwaniu zależności i że mówi się, że klasy bezpośrednio zależą od siebie, gdy jedna z nich tworzy instancję drugiej, aby uzyskać dostęp do funkcjonalności typu docelowego. Aby oddzielić bezpośrednią zależność między tymi dwoma typami, należy utworzyć interfejs. Interfejs daje typowi możliwość dołączania różnych implementacji, w zależności od kontekstu wymaganej funkcjonalności. Przekazując typ interfejsu do konstruktora klasy lub metody, klasa/metoda potrzebująca danej funkcjonalności nie zna żadnych szczegółów na temat typu implementującego interfejs. Z tego powodu typ interfejsu może być ponownie używany w różnych klasach, które wymagają podobnego, ale nie identycznego zachowania.
  • Aby poeksperymentować ze wstrzykiwaniem zależności, spójrz na swój kod z jednej lub większej liczby aplikacji i spróbuj przekonwertować często używany typ podstawowy na interfejs.

  • Zmień klasy, które bezpośrednio tworzą instancję tego typu podstawowego, aby korzystały z nowego typu interfejsu, i przekaż go przez konstruktor lub listę parametrów metody klasy, która będzie go używać.

  • Utwórz implementację testową, aby przetestować ten typ interfejsu. Po refaktoryzacji kod DIstanie się łatwiejszy do wdrożenia i zauważysz, o ile bardziej elastyczna stanie się Twoja aplikacja pod względem ponownego użycia i łatwości konserwacji.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION