Odwrócenie 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 Flow of Control jest „odbierany” z ogólnej biblioteki lub kodu wielokrotnego użytku.
Aby lepiej to zrozumieć, zobaczmy, jak kodowaliśmy we wczesnych dniach kodowania. W językach proceduralnych / tradycyjnych logika biznesowa generalnie steruje przepływem aplikacji i „wywołuje” ogólny lub wielokrotnego użytku kod / funkcje. 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
W przeciwieństwie do IoC, struktury są kodem wielokrotnego użytku, który „wywołuje” logikę biznesową.
Na przykład w systemie opartym na systemie Windows platforma będzie już dostępna do tworzenia elementów interfejsu użytkownika, takich jak przyciski, menu, okna i okna dialogowe. Kiedy piszę logikę biznesową mojej aplikacji, byłyby to zdarzenia frameworka, które wywołałyby mój kod logiki biznesowej (gdy zdarzenie jest wyzwalane), a NIE odwrotnie.
Chociaż kod frameworka nie jest świadomy 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 Sterowanie przepływem jest „odwrócone”.
Tak więc zamiast uzależniać przepływ sterowania od statycznie powiązanych obiektów, przepływ zależy od ogólnego wykresu obiektu 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 nie tylko.
Dependency Injection sugeruje, że zamiast klas zależnych (tutaj Class Car) tworzących swoje zależności (Class Engine i class Tire), klasa powinna zostać wstrzyknięta konkretną instancją zależności.
Przyjrzyjmy się bardziej praktycznemu przykładowi. Weź pod uwagę, że piszesz własny TextEditor. Między innymi możesz mieć moduł sprawdzania pisowni, który zapewnia użytkownikowi możliwość sprawdzenia 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 jakiś tekst. Deweloper przechwyci tekst i wywoła funkcję CheckSpellings oraz znajdzie listę literówek, które pokaże użytkownikowi.
Wydaje się, że wszystko działa świetnie, aż pewnego pięknego dnia 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" SpellChecker, który jest ściśle powiązany 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ść. Ponadto nasz edytor tekstu potrzebuje sposobu na przechowywanie konkretnych odniesień 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 klasa powinna zostać wstrzyknięta wraz z jej zależnościami. Zatem to kod wywołujący powinien być odpowiedzialny za wstrzyknięcie wszystkich zależności do wywoływanej klasy / kodu. Więc możemy zmienić strukturę naszego kodu 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 wstrzyknąć w konstruktorze, właściwości publicznej lub metodzie.
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 wywołujący kod podczas tworzenia edytora tekstu mógł wstrzyknąć odpowiedni typ sprawdzania pisowni do instancji TextEditor.
Możesz przeczytać cały artykuł tutaj