Inwersja kontroli to ogólna zasada projektowania architektury oprogramowania, która pomaga w tworzeniu łatwych w utrzymaniu modułowych struktur oprogramowania wielokrotnego użytku.
Jest to zasada projektowa, w której przepływ kontroli jest „odbierany” z biblioteki zapisanej w formie ogólnej lub kodu wielokrotnego użytku.
Aby to lepiej zrozumieć, zobaczmy, jak kodowaliśmy w naszych wcześniejszych czasach kodowania. W językach proceduralnych / tradycyjnych logika biznesowa ogólnie kontroluje przepływ aplikacji i „wywołuje” ogólny kod lub funkcje wielokrotnego użytku. Na przykład w prostej aplikacji konsoli mój przepływ kontroli jest kontrolowany przez instrukcje mojego programu, które mogą obejmować wywołania niektórych ogólnych funkcji wielokrotnego użytku.
print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);
//More print and scan statements
<Do Something Interesting>
//Call a Library function to find the age (common code)
print Age
Natomiast w przypadku IoC frameworki to kod wielokrotnego użytku, który „wywołuje” logikę biznesową.
Na przykład w systemie opartym na systemie Windows dostępna będzie już platforma do tworzenia elementów interfejsu użytkownika, takich jak przyciski, menu, okna i okna dialogowe. Kiedy piszę logikę biznesową mojej aplikacji, to zdarzenia frameworka będą wywoływały mój kod logiki biznesowej (po uruchomieniu zdarzenia), a NIE odwrotnie.
Chociaż kod frameworka nie zna mojej logiki biznesowej, nadal będzie wiedział, jak wywołać mój kod. Osiąga się to za pomocą zdarzeń / delegatów, wywołań zwrotnych itp. Tutaj kontrola przepływu jest „odwrócona”.
Zatem zamiast zależeć od przepływu kontroli na statycznie związanych obiektach, przepływ zależy od ogólnego wykresu obiektów i relacji między różnymi obiektami.
Dependency Injection to wzorzec projektowy, który implementuje zasadę IoC do rozwiązywania zależności obiektów.
Mówiąc prościej, kiedy próbujesz pisać kod, będziesz tworzyć i używać różnych klas. Jedna klasa (klasa A) może używać innych klas (klasa B i / lub D). Tak więc klasy B i D są zależnościami klasy A.
Prostą analogią będzie samochód klasy. Samochód może zależeć od innych klas, takich jak silnik, opony i inne.
Wstrzykiwanie zależności sugeruje, że zamiast klas zależnych (tutaj samochód klasy) tworzących zależności (silnik klasy i opona klasy), klasy należy wstrzyknąć konkretny przykład zależności.
Pozwala zrozumieć bardziej praktyczny przykład. Zastanów się, czy piszesz własnego TextEditora. Między innymi możesz mieć moduł sprawdzania pisowni, który zapewnia użytkownikowi możliwość sprawdzania literówek w jego tekście. Prostą implementacją takiego kodu może być:
Class TextEditor
{
//Lot of rocket science to create the Editor goes here
EnglishSpellChecker objSpellCheck;
String text;
public void TextEditor()
{
objSpellCheck = new EnglishSpellChecker();
}
public ArrayList <typos> CheckSpellings()
{
//return Typos;
}
}
Na pierwszy rzut oka wszystko wygląda różowo. Użytkownik napisze tekst. Deweloper przechwyci tekst i wywoła funkcję CheckSpellings i znajdzie listę literówek, które pokaże użytkownikowi.
Wydaje się, że wszystko działa świetnie, aż pewnego pięknego dnia, gdy jeden użytkownik zacznie pisać po francusku w edytorze.
Aby zapewnić obsługę większej liczby języków, potrzebujemy więcej sprawdzania pisowni. Prawdopodobnie francuski, niemiecki, hiszpański itp.
Tutaj stworzyliśmy ściśle powiązany kod z „angielskim” SpellCheckerem ściśle powiązanym z naszą klasą TextEditor, co oznacza, że nasza klasa TextEditor jest zależna od EnglishSpellChecker lub innymi słowy EnglishSpellCheker jest zależnością dla TextEditor. Musimy usunąć tę zależność. Co więcej, nasz edytor tekstu potrzebuje sposobu, aby zachować konkretne odniesienie do dowolnego modułu sprawdzania pisowni w oparciu o uznanie programisty w czasie wykonywania.
Tak więc, jak widzieliśmy we wprowadzeniu DI, sugeruje, że klasie należy wstrzyknąć swoje zależności. Zatem obowiązkiem kodu wywołującego jest wstrzyknięcie wszystkich zależności do wywoływanej klasy / kodu. Możemy więc zrestrukturyzować nasz kod jako
interface ISpellChecker
{
Arraylist<typos> CheckSpelling(string Text);
}
Class EnglishSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
Class FrenchSpellChecker : ISpellChecker
{
public override Arraylist<typos> CheckSpelling(string Text)
{
//All Magic goes here.
}
}
W naszym przykładzie klasa TextEditor powinna otrzymać konkretną instancję typu ISpellChecker.
Teraz zależność można wprowadzić do Konstruktora, własności publicznej lub metody.
Spróbujmy zmienić naszą klasę za pomocą Constructor DI. Zmieniona klasa TextEditor będzie wyglądać mniej więcej tak:
Class TextEditor
{
ISpellChecker objSpellChecker;
string Text;
public void TextEditor(ISpellChecker objSC)
{
objSpellChecker = objSC;
}
public ArrayList <typos> CheckSpellings()
{
return objSpellChecker.CheckSpelling();
}
}
Aby kod wywołujący podczas tworzenia edytora tekstu mógł wstrzyknąć odpowiedni typ sprawdzania pisowni do instancji TextEditor.
Możesz przeczytać cały artykuł tutaj