Im siebten Wissenshäppchen geht es um das Dependency Inversion Principle.
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.
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.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
enddef pay_employees
@repo.find_all.each { |employee| employee.pay }
end
endclass MySqlEmployeeRepository
def find_all
puts "Accessing the database and reading all employees"
[Employee.new] * 10
end
endclass Employee
def pay
puts "Thank you for paying me"
end
endPayroll.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
enddef pay_employees
@repo.find_all.each { |employee| employee.pay }
end
endclass MySqlEmployeeRepository
def find_all
puts "Accessing the database and reading all employees"
[Employee.new] * 10
end
endclass FakeEmployeeRepository
def find_all
[Employee.new] * 1
end
endclass Employee
def pay
puts "Thank you for paying me"
end
endputs "Production:"
Payroll.new(MySqlEmployeeRepository.new).pay_employeesputs "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
endputs "Using Oracle:"
Payroll.new(OracleEmployeeRepository.new).pay_employees# Reading employees from Oracle database
# Thank you for paying me
# ...
# Thank you for paying me
Wie sollte es anders sein: Auch für das letzte SOLID-Prinzip empfehle ich noch einmal Clean Code* von Uncle Bob.