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:

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
House brickHouse = new BrickHouse();

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Dom z cegieł

20000

House brickHouse = new BrickHouse();

  brickHouse = new SecondFloor(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Dom murowany + drugie piętro

40000

House brickHouse = new BrickHouse();


  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());

Dom murowany + piętro + garaż

45000

House woodenHouse = new SecondFloor(new Garage(new WoodenHouse()));

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

Drewniany dom + garaż + drugie piętro

50000

House woodenHouse = new WoodenHouse();

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());

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.