Dependency inversion principle
Ta zasada mówi o tym, że moduły wysokiego poziomu nie powinny być zależne od modułów niskiego poziomu. Oba powinny zależeć od abstrakcji. Natomiast abstrakcje nie powinny zależeć od szczegółów. Szczegóły powinny natomiast też zależeć od abstrakcji. Tak naprawdę moduły wysokiego poziomu oddzielamy od modułów niskiego poziomu warstwą abstrakcji. Tutaj często będziemy korzystać z wstrzykiwania zależności.
Przykład złamania tej zasady:
public class ReaderInfo {
private Comics comics;
private Science science;
public ReaderInfo() {
this.comics = new Comics();
this.science = new Science();
}
public void rentalInfo(TypeBooks typeBooks){
switch(typeBooks)
{
case COMICS:
this.comics.showInfo();
break;
case SCIENCE:
this.science.showInfo();
break;
default:
break;
}
}
}
public class Comics {
public void showInfo(){
System.out.println("Wypożyczono komiks.");
}
}
public class Science {
public void showInfo(){
System.out.println("Wypożyczono książkę popularnonaukową.");
}
}
public enum TypeBooks {
COMICS,SCIENCE
}
Załóżmy, że mamy klasę ReaderInfo która ma służyć do pokazania informacji dotyczącej jaką książkę wypożyczono. Zwróćmy uwagę, że w konstruktorze tej klasy tworzymy dwa nowe obiekty: comics oraz science. Czyli moduł wysokiego poziomu jest zależny od modułów niższego poziomu. Co już łamie zasadę. Zwróćmy uwagę, że jeśli dodam kolejną klasę np Album to w klasie ReaderInfo muszę w konstruktorze tworzyć instancje tej klasy i dodatkowo w metodzie rentalInfo powinnam dodać kolejnego case. (Pomijam nowy enum).
Przykład zastosowania zasady:
Aby zastosować zasadę stworzyłam interface Book w którym mam tylko jedną metodę rentalInfo. Każdy typ książki którą stworzyłam, bądź stworzę będzie implementował ten interface.
public interface Book {
void rentalInfo();
}
public class Comics implements Book{
@Override
public void rentalInfo() {
System.out.println("Wypożyczono komiks!");
}
}
public class Science implements Book{
@Override
public void rentalInfo() {
System.out.println("Wypożyczono książkę popularnonaukową!");
}
}
public class ReaderInfo {
private List<Book> books;
public ReaderInfo(List<Book> books) {
this.books = books;
}
public void rentalInfo(){
for(Book book:books){
book.rentalInfo();
}
}
}
W klasie ReaderInfo nastąpiły zmiany: W konstruktorze tworze listę książek (Book), a metodę rentalInfo wywołuje na obiektach book. Tym samym on sam będzie wiedział z której klasy (Comics lub Science) wywołać metodę. W przypadku dodania nowego typu książek który implementuje nasz interface nie muszę zmieniać klasy wyższego poziomu.
Zobaczmy jak wygląda main:
public class Main {
public static void main(String[] args){
List<Book> books = Arrays.asList(new Comics(),new Science(),new Comics());
ReaderInfo readerInfo = new ReaderInfo(books);
readerInfo.rentalInfo();
}
}