Dependency Inversion Principle (DIP) – Wissenshäppchen #7

Dependency Inversion Principle (DIP) – Wissenshäppchen #7

Im siebten Wissenshäppchen geht es um das Dependency Inversion Principle. Inhalt Das DIP ist das letzte der fünf SOLID-Prinzipien. High level modules should not depend upon low level modules. Both should depend upon abstractions.
18 Minuten

Beschreibung

vor 6 Jahren

Im siebten Wissenshäppchen geht es um das Dependency
Inversion Principle.
Inhalt

Das DIP ist das letzte der fünf SOLID-Prinzipien.


High level modules should not depend upon low level modules. Both
should depend upon abstractions.


Oder:


Abstractions should not depend upon details. Details should
depend upon abstractions.


Welche Abhängigkeiten werden hier „umgedreht“? Komponenten, die
andere Komponenten benötigen, erzeugen sich diese nicht selbst,
sondern bekommen sie von außen hineingegeben. Braucht ein Service
beispielsweise ein Repository um arbeiten zu können, erzeugt er
es sich nicht selbst, z.B. mit this.repo = new Repository() in
seinem Konstruktor, sondern bekommt es stattdessen schon fertig
als Parameter übergeben, z.B. public Service(Repository repo).
Das heißt, obwohl die Abhängigkeit vom Service zum Repository
geht, wird sie „umgekehrt“ aufgelöst, indem das Repository dem
Service übergeben wird.
Erklärung

Wenn Komponenten sich ihre Abhängigkeiten selbst erzeugen,
sind diese später nur umständlich oder gar nicht mehr
austauschbar.

Tests von Komponenten, die sich ihre Abhängigkeiten selbst
erzeugen, sind schwierig/unmöglich/langsam/fehleranfällig, da die
Abhängigkeiten nicht so einfach gegen Fake-Objekte ausgetauscht
werden können.

Die Abhängigkeiten sind nicht explizit in der API sichtbar.
Der obige Service „braucht“ ein Repository, aber das sieht man
nur, wenn man sich den Quelltext von Service anschaut. Beim
Erzeugen eines Services ist dies nicht offensichtlich, weil er
sich das Repository „heimlich“ selbst erzeugt.

Beispiel

Um alle Employees zu bezahlen, erzeugt sich die Payroll ihr
eigenes MySqlEmployeeRepository. Dadurch wird auch im Test die
echte Datenbank verwendet und ein Austauschen gegen eine
Oracle-Datenbank erfordert eine Anpassung der Klasse Payroll
(vgl. Open Closed Principle).
class Payroll def initialize @repo =
MySqlEmployeeRepository.new end def pay_employees
@repo.find_all.each { |employee| employee.pay } end end class
MySqlEmployeeRepository def find_all puts "Accessing the database
and reading all employees" [Employee.new] * 10 end end class
Employee def pay puts "Thank you for paying me" end end
Payroll.new.pay_employees # Accessing the database and reading all
employees # Thank you for paying me # ... # Thank you for paying me

Statt dieser „harten“ und versteckten Abhängigkeit sollte Payroll
sich lieber die Implementierung eines Repositorys liefern lassen.
Das erfordert nur eine kleine Umstrukturierung des Codes und
ermöglicht deutlich einfachere Tests.
class Payroll def initialize(repo) @repo = repo end def
pay_employees @repo.find_all.each { |employee| employee.pay } end
end class MySqlEmployeeRepository def find_all puts "Accessing the
database and reading all employees" [Employee.new] * 10 end end
class FakeEmployeeRepository def find_all [Employee.new] * 1 end
end class Employee def pay puts "Thank you for paying me" end end
puts "Production:"
Payroll.new(MySqlEmployeeRepository.new).pay_employees puts "Test:"
Payroll.new(FakeEmployeeRepository.new).pay_employees # Production:
# Accessing the database and reading all employees # Thank you for
paying me # ... # Thank you for paying me # Test: # Thank you for
paying me

Außerdem kann Payroll nun auch ohne Anpassungen (!) mit einer
Oracle-Datenbank arbeiten.
class OracleEmployeeRepository def find_all puts "Reading
employees from Oracle database" [Employee.new] * 100 end end puts
"Using Oracle:"
Payroll.new(OracleEmployeeRepository.new).pay_employees # Reading
employees from Oracle database # Thank you for paying me # ... #
Thank you for paying me Vorteile

Abhängigkeiten sind explizit sichtbar und können bei
geänderten Anforderungen oder in Tests einfach ausgetauscht
werden.

Neue Funktionalitäten können dem Programm einfacher
hinzugefügt werden, da durch die Abhängigkeit von Abstraktionen
oftmals auch das OCP umgesetzt wird.

Nachteile

Ein Problem könnte wieder ein „Overengineering“ sein. Es ist
wahrscheinlich nicht sinnvoll, jede Kleinigkeit hinter einer
Abstraktion zu verbergen. Aber wie entscheidet man, welche
Abstraktion sinnvoll ist?

Bei vielen Abhängigkeiten wird das Erstellen von Objekten
(und damit auch deren Test) schwierig, weil viel Arbeit vorab
erledigt werden muss. Hier könnte dann ein DI-Container zum
Einsatz kommen.

Literaturempfehlungen

Wie sollte es anders sein: Auch für das letzte SOLID-Prinzip
empfehle ich noch einmal Clean Code* von Uncle Bob.


*
Links

Permalink zu dieser Podcast-Episode

RSS-Feed des Podcasts

Dependency Inversion Principle

ArticleS.UncleBob.PrinciplesOfOod (Artikel von Uncle Bob,
Beispiel in C++)

Dependency-Inversion-Prinzip – Wikipedia

Das Dependency Inversion Principle | Informatik Aktuell
(Deutsches Beispiel in C#)

Kommentare (0)

Lade Inhalte...

Abonnenten

15
15