Przeszliśmy już przez użycie obiektu klasy jako Singletona, ale możesz jeszcze nie podejrzewać, że jest to jeden z wzorców projektowych i jeden z najczęściej używanych.
W rzeczywistości istnieje wiele takich samych wzorców, ponadto mają one hierarchię, a każdy wzorzec ma swój własny cel.
Klasyfikacja wzorców
Typ wzoru | Stosowalność |
---|---|
Generatywny | Typ, który rozwiązuje problem tworzenia obiektów |
Strukturalny | Wzorce, które pozwalają nam zbudować poprawną, rozszerzalną hierarchię klas w naszej architekturze |
behawioralne | To skupisko wzorców odpowiada za bezpieczną i wygodną interakcję między obiektami programu. |
Zazwyczaj wzorzec charakteryzuje się problemem, który rozwiązuje. Przyjrzyjmy się kilku wzorcom, z którymi najczęściej spotykamy się podczas pracy z Javą:
wzór | zamiar |
---|---|
Singel | Jesteśmy już z nim zaznajomieni i używamy go do tworzenia i odwoływania się do klasy, która nie może mieć więcej niż jednej instancji. |
Iterator | Nawiasem mówiąc, my również go znamy i wiemy, że ten wzorzec pozwala nam iterować po złożonym obiekcie bez ujawniania jego wewnętrznej reprezentacji. Używany z kolekcjami. |
Adapter | Łączy niekompatybilne obiekty, aby ze sobą współpracowały. Myślę, że mówiąc o adapterze, każdy wyobraża sobie dokładnie, co robi ten wzór. Prostym przykładem z życia jest adapter USB - wtyczka w gniazdku. |
Metoda szablonowa |
Wzorzec programowania behawioralnego, który rozwiązuje problem integracji i pozwala zmieniać jego kroki bez zmiany struktury algorytmu. Wyobraź sobie, że mamy algorytm składania samochodu w postaci sekwencji składania: Podwozie -> Nadwozie -> Silnik -> montaż wnętrza Jeśli włożymy wzmocnioną ramę, mocniejszy silnik lub wnętrze z dodatkowym oświetleniem, nadal nie zmienimy algorytmu, a abstrakcyjna sekwencja pozostanie taka sama. |
dekorator | Tworzy opakowania dla obiektów, aby dodać do nich użyteczną funkcjonalność. Rozważymy to w ramach tego artykułu. |
W Java.io wzorce są zaimplementowane w następujących klasach:
wzór | Gdzie używany w Java.io |
---|---|
Adapter |
|
Metoda szablonowa | |
dekorator |
Dekorator wzorów
Wyobraźmy sobie, że opisujemy model projektu domu.
Ogólnie schemat będzie wyglądał następująco:
Początkowo mamy do wyboru kilka typów domków. Minimalna konfiguracja to jedno piętro z dachem, wtedy przy pomocy wszelkiego rodzaju dekoratorów możemy zmienić dodatkowe parametry, a to wpłynie na cenę domu.
Utwórz abstrakcyjną lekcję domową:
public abstract class House {
String info;
public String getInfo() {
return info;
}
public abstract int getPrice();
}
Tutaj mamy 2 metody:
- getInfo() zwraca informację o nazwie i wyposażeniu naszego domu;
- getPrice() - cena domu w aktualnej konfiguracji.
Posiadamy również standardowe realizacje domów - murowane i drewniane:
public class BrickHouse extends House {
public BrickHouse() {
info = "Brick House";
}
@Override
public int getPrice() {
return 20_000;
}
}
public class WoodenHouse extends House {
public WoodenHouse() {
info = "Wooden House";
}
@Override
public int getPrice() {
return 25_000;
}
}
Obie klasy dziedziczą z klasy Dom i zastępują metodę ceny, ustawiając niestandardową cenę dla standardowego domu. Przypisz nazwę w konstruktorze.
Następnie musimy napisać klasy - dekoratorów. Są to klasy, które również dziedziczą po klasie House . W tym celu tworzymy abstrakcyjną klasę dekoratora.
W nim możemy umieścić dodatkową logikę zmiany obiektu. W naszym przypadku nie będzie dodatkowej logiki, a klasa abstrakcyjna będzie pusta.
abstract class HouseDecorator extends House {
}
Następnie tworzymy klasy implementacji dekoratora. Stworzymy kilka klas, które pozwolą nam dodać dodatkowe parametry do domu:
public class SecondFloor extends HouseDecorator {
House house;
public SecondFloor(House house) {
this.house = house;
}
@Override
public int getPrice() {
return house.getPrice() + 20_000;
}
@Override
public String getInfo() {
return house.getInfo() + " + second floor";
}
}
Dekorator, który dobuduje drugie piętro do naszego domu |
Konstruktor dekoratora akceptuje dom, do którego stosujemy modyfikacje dekoratora. I nadpisujemy metody getPrice() i getInfo() , zwracając informacje o nowym zmodernizowanym domu na podstawie starego.
public class Garage extends HouseDecorator {
House house;
public Garage(House house) {
this.house = house;
}
@Override
public int getPrice() {
return house.getPrice() + 5_000;
}
@Override
public String getInfo() {
return house.getInfo() + " + garage";
}
}
Dekorator, który dobuduje garaż do naszego domu |
Teraz możemy odmienić nasz dom z dekoratorami. Aby to zrobić, musisz stworzyć dom:
House brickHouse = new BrickHouse();
Następnie przypisujemy do naszej zmiennejdomnowa wartość w postaci dekoratora, któremu przekazujemy nasz dom:
brickHouse = new SecondFloor(brickHouse);
Nasza zmiennadomma już dom z drugim piętrem.
Spójrzmy na przypadki użycia dekoratora:
Przykład kodu | Wniosek |
---|---|
|
Dom z cegieł 20000 |
|
Dom murowany + drugie piętro 40000 |
|
Dom murowany + piętro + garaż 45000 |
|
Drewniany dom + garaż + drugie piętro 50000 |
|
Drewniany dom 25000 Drewniany dom + garaż 30000 |
W tym przykładzie widzimy korzyści płynące z ulepszenia obiektu za pomocą dekoratora. Okazuje się, że nie zmieniliśmy samego obiektudrewniany domi na jego podstawie utworzyliśmy nowy obiekt. Ale dzięki tej przewadze możemy również rozważyć wady: za każdym razem tworzymy nowy obiekt w pamięci, co daje jej dodatkowe obciążenie.
Rozważ diagram UML naszego programu:
Dekorator jest zaimplementowany w bardzo prosty sposób i służy do dynamicznej zmiany obiektów i ich modernizacji. Dekorator może zostać rozpoznany przez konstruktory, które jako parametry przyjmują obiekty tego samego typu abstrakcyjnego lub interfejsu, co bieżąca klasa. W Javie ten wzorzec jest szeroko stosowany w klasach I/O.
Na przykład, jak już zauważyliśmy, wszystkie podklasy java.io.InputStream , OutputStream , Reader i Writer mają konstruktor, który akceptuje obiekty tych samych klas.
GO TO FULL VERSION