O co chodzi z odwróceniem zależności

Jestem aktualnie w trakcie lektury „Czysta architektura” Wujka Boba. Natknęłam się w książce na część wyjaśniającą odwrócenie zależności (ostatnią z zasad SOLID) w całkiem przystępny sposób.

Jak brzmi ta zasada?

Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych.

Zależności między nimi powinny wynikać z abstrakcji.

Fajnie, ale o co konkretnie chodzi? Co to są te moduły wysokopoziomowe albo niskopoziomowe? O jakiej abstrakcji jest mowa?

Wszystko można wyjaśnić za pomocą kilku klas i wykresów.

Przykład nr 1 – jak nie robić

Wyobraźmy sobie, że mamy program, który wykonuje jakąś logikę. W przypadku najprostrzej aplikacji konsolowej mamy metodę:

  • Main(), która wywołuje jakąś metodę
  • DoSomething(), a ta z kolei w zależności od parametru wywołuje metody
  • WriteSomethingGood() albo WriteSomethingBad()

Konkretne klasy mogłyby wyglądać tak:

  • Program.cs
class Program
{
	void Main()
	{
		bool isGoodMood = false;
		var moodLogic = new MoodLogic();
		moodLogic.DoSomething(isGoodMood);
	}
}
  • MoodLogic.cs
class MoodLogic
{
	void DoSomething(bool isGoodMood)
	{
		var logic = new Logic();
		if (isGoodMood)
		{
			logic.WriteSomethingGood();
		}
		else
		{
			logic.WriteSomethingBad();
		}
	}
}
  • Logic.cs
class Logic
{
	void WriteSomethingGood()
	{
		Console.WriteLine(":)");
	}

	void WriteSomethingBad()
	{
		Console.WriteLine(":(");
	}
}

W skrócie można by to rozrysować następująco:

moodDiagram

Czarne strzałki ukazują zależności między klasami. Strzałki niebieskie odwzorowują przepływ sterowania – od głównej metody, poprzez funkcję wysokiego poziomu (która jeszcze nie robi konkretnej, widocznej akcji), aż do funkcji niskiego poziomu (które wykonują konkretną akcję, w naszym przypadku wypisują tekst w konsoli).

Jak widać, w tym przykładzie wszystko idzie od góry do dołu. Czy to dobrze? Niekoniecznie. A co zrobimy, gdy okaże się, że mamy jeszcze inny nastrój? Nie tylko Good i Bad? Ale może Happy? Angry? Itp.? Wtedy nasza zmienna isGoodMood będzie niewystarczająca. Tak samo niewystarczające będą metody WriteSomethingGood() i WriteSomethingBad().

Powyższe rozwiązanie nie daje więc nam za bardzo możliwości rozbudowy naszego kodu. Żeby go zmienić, trzeba przekształcać cały kod – a to sporo pracy.

Może więc da radę już podczas tworzenia rozwiązania coś zmienić, żeby nie mieć takich problemów?

Przykład nr 2 – jak robić

Stwórzmy interfejs IMood.cs, który będzie pewnym poziomem abstrakcji w naszym rozwiązniu:

interface IMood
{
	void WriteSomething();
}

Zmieńmy klasę Logic na 2 osobne klasy implementujące interfejs IMood:

  • GoodMood.cs
class GoodMood : IMood
{
	void WriteSomething()
	{
		Console.WriteLine(":)");
	}
}
  • BadMood.cs
class BadMood : IMood
{
	void WriteSomething()
	{
		Console.WriteLine(":(");
	}
}

Jak teraz będą wyglądać pozostałe klasy?

  • Program.cs
class Program
{
	void Main()
	{
		var mood = new GoodMood();
		DoSomething(mood);
	}
}
  • MoodLogic.cs
static class MoodLogic
{
	void DoSomething(IMood mood)
	{
		mood.WriteSomething();
	}
}

Jak widać, działanie programu nie zmieni się (ciągle zostanie wypisana na konsolę uśmiechnięta buźka), ale sporo kodu się zmieniło. Powstał jakiś interfejs (abstrakcja), zamiast zmiennej bool przekazujemy obiekt implementujący ten interfejs… Po co tak kombinować?

Może zerknijmy jeszcze na wykres:

moodInt

To, co się zmieniło, to obecność interfejsu IMood, do którego bezpośrednio istnieje zależność z metody WriteSomething(). Jak widać przepływ sterowania (niebieska strzałka) różni się od przepływu zależności (czarna strzałka). Dodatkowo kluczową rolę gra czerwona strzałka. Ona wskazuje właśnie ODWRÓCENIE ZALEŻNOŚCI (Dependency inversion). Wskazuje w kierunku przeciwnym do przepływu sterowania. A taka zmiana robi różnicę.

Jak teraz moglibyśmy rozszerzyć aplikację o kolejne nastroje? Czy byłoby to łatwe, czy trudne? Zdecydowanie łatwiejsze, niż w poprzednim przypadku! Wystarczyło by tylko utworzyć nową klasę NewMood, która implementowałaby interfejs IMood i wypisywała na konsolę np. taką buźkę 😀.


Podoba Ci się to, co tworzę? Chcesz dostawać informacje o:
– wydarzeniach, które organizuję lub wspieram (np. konferencje, meetupy, webinary)
– inicjatywach, które organizuję lub wspieram (np. GeekWeekWro, DevAdventCalendar)
– moich prelekcjach, kursach i szkoleniach
– wyróżnionych artykułach z mojego bloga

0% SPAMu, 100% informacji! Krótko i na temat.

8 uwag do wpisu “O co chodzi z odwróceniem zależności

  1. Kamil Łucyszyn

    Cześć, super wyjaśnione! Przykład jest super stworzony.

    Jedna drobna uwaga, żeby przykład zadziałał klasę MoodLogic trzeba zainicjować, albo zrobić ją statyczną : ) może by warto to poprawić, żeby kod w przykładzie się kompilował 😀

    Polubione przez 1 osoba

  2. Pingback: Rok 2020 podsumowanie (w liczbach) – Programmer-girl

Dodaj komentarz