- natalia.kaczynska.programista@gmail.com
Dependency Inversion Principle
Głównym celem powyższej zasady jest uniezależnienie wysokopoziomowych modułów (tych odpowiedzialnych za logikę biznesową) od szczegółowych implementacji niskopoziomowych (np. pracy z bazą danych, systemem plików czy interfejsem użytkownika). Zamiast tego zarówno jedne, jak i drugie powinny opierać się na wspólnych abstrakcjach.
W praktyce oznacza to, że nie powinniśmy tworzyć ścisłych powiązań pomiędzy klasami — wysokopoziomowe komponenty nie powinny „znać” szczegółów technicznych działania modułów pomocniczych. Dzięki temu nasz kod staje się:
bardziej elastyczny – łatwiej podmienić jedną implementację na inną (np. bazę danych na API),
bardziej odporny na zmiany – modyfikacja niskopoziomowych klas nie wpływa bezpośrednio na całą logikę systemu.
Zanim przejdziemy do przykładu kodu, warto zapamiętać krótką definicję DIP: „Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Oba typy modułów powinny zależeć od abstrakcji. Abstrakcje nie powinny zależeć od szczegółów. Szczegóły (konkretne implementacje) powinny zależeć od abstrakcji.”
Można to porównać do korzystania z gniazdka elektrycznego. Urządzenia takie jak czajnik, komputer czy ładowarka telefonu nie są bezpośrednio dopasowane do konkretnego źródła prądu w elektrowni. Zamiast tego wszystkie urządzenia używają abstrakcji – czyli standardowej wtyczki i gniazdka. Dzięki temu:
każde urządzenie może działać w dowolnym domu czy biurze,
możemy wymienić urządzenie bez przebudowy instalacji elektrycznej,
sama elektrownia nie musi „znać” szczegółów, jakie urządzenia będą korzystać z prądu.
Tak samo w programowaniu – nasze moduły wysokopoziomowe powinny „podłączać się” do abstrakcji (interfejsów), a nie bezpośrednio do szczegółowych implementacji.
Poniżej prezentuję przykład, który łamie zasadę DIP:
Moduł niskopoziomowy:
class MySQLDatabase {
void query(String sql) {
System.out.println("Executing MySQL Query: " + sql);
}
}
Moduł wysokopoziomowy:
public class UserService {
private MySQLDatabase db;
public UserService{
this.db = new MySQLDatabase();
}
public void getUser(int id) {
db.query("SELECT * FROM users WHERE id = " + id);
}
}
Poniżej poprawna wersja:
interface Database {
void query(String sql);
}
class MySQLDatabase implements Database{
public void query(String sql) {
System.out.println("Executing MySQL Query: " + sql);
}
}
class UserService {
private Database db;
public UserService(Database db) {
this.db = db;
}
public void getUser(int id) {
db.query("SELECT * FROM users WHERE id = " + id);
}
}