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)
Melde dich an, um einen Kommentar zu schreiben.