Skip to main content

wzorzec dekorator:

Wzorzec dekorator – wzorzec ten należy do grupy wzorców strukturalnych, który pozwala na dodawanie nowych zachowań lub obowiązków obiektom poprzez umieszczenie tych obiektów w tzw. dekoratorach, które zawierają nowe zachowania. Dodatkowo dekoratory dają nam elastyczność podobną do tej które daje dziedziczenie, ale oferują jednak w zamian znacznie rozszerzoną funkcjonalność.

Zastosowanie wzorca:

  • gdy chcemy przypisywać dodatkowe obowiązki obiektom w trakcie działania programu bez konieczności psucia kodu, który z tych obiektów korzysta
  • jeśli uznamy rozszerzenie zakresu obowiązków poprzez dziedziczenie za niekorzystne lub niemożliwe do zrealizowania

Aby lepiej zrozumieć działanie tego wzorca rozważmy następującą sytuację:

v2021. Dodatkowo producent do każdego z podstawowych modeli pozwala wybrać pewne dodatki: czujnik parkowania, elektrycznie sterowane lusterka, elektrycznie sterowane szyby, kamerę cofania, klimatyzację, podgrzewanie lusterek bocznych, nowoczesne radio samochodowe oraz wielofunkcyjną kierownicę. Po dokonaniu wyboru strona powinna nas poinformować o cenie samochodu oraz podać jego krótki opis.

Naszym zadaniem będzie napisanie programu, który poinformuje nas o tym co wybraliśmy i jaka jest tego cena. Rozważymy ten problem na dwa sposoby.

  1. Bez użycia wzorca dekorator

W pierwszej kolejności zerknijmy na diagram klas (tu ograniczyłam się do trzech dodatków, bo gdybym tego nie zrobiła diagram zawierałby aż 256 podklas, a tak mamy tylko 16).

diagram car

Możemy już zauważyć, że dodanie nowego modelu auta czy też nowego dodatku spowoduje, że będziemy zmuszeni dopisywać bardzo dużo nowych klas. Co w pewnym momencie może być trudne i łatwo będzie można popełnić błąd mimo iż każda z klas ma tylko dwie metody. Z kolei dodanie nowej metody abstrakcyjnej do klasy Car również spowoduje, że będziemy musieli zaimplementować ją w każdej klasie która ją rozszerza.

Zobaczmy zatem implementacje dowolnej klasy:

package RaSha2020;

import RaSha2021.Car;

public class RaSha2020AirConditioningWindows extends Car {
    @Override
    public void description() {
        System.out.println("Model: RaSha ver.2020, klimatyzacja, elektryczne szyby");
    }

    @Override
    public double cost() {
        return 24500.00;
    }
}

Zobaczmy implementację wykonania klasy CarMain, gdzie z góry już ustaliłam wybór aut oraz dodatków.

import RaSha2021.RaSha2021;
import RaSha2021.Car;
import RaSha2020.RaSha2020SteeringWheel;
import RaSha2021.RaSha2021AirConditioningSteeringWheel;

public class CarMain {
    public static void main(String[] args){

        Car car = new RaSha2021();
        car.description();
        System.out.println("Koszt: "+car.cost()+" zł");

        System.out.println();

        Car auto = new RaSha2020SteeringWheel();
        auto.description();
        System.out.println("Koszt: "+auto.cost()+" zł");

        System.out.println();

        Car car1 = new RaSha2021AirConditioningSteeringWheel();
        car1.description();
        System.out.println("Koszt: "+car1.cost()+" zł");
    }
}

Zatem w rezultacie powinniśmy dostać podstawową wersje RaSha ver.2021,RaSha ver.2020 z wielofunkcyjną kierownicą oraz RaSha ver.2021 z klimatyzacją oraz wielofunkcyjną kierownicą. Zobaczmy to w konsoli:

Model: RaSha ver.2021
Koszt: 20000.0 zł

Model: RaSha ver.2020, wielofunkcyjna kierownica
Koszt: 18000.0 zł

Model: RaSha ver.2021, klimatyzacja, wielofunkcyjna kierownica
Koszt: 28000.0 zł
  1. Z użyciem wzorca

Diagram klas:

diagram car decorator

Na diagramie umieściłam wszystkie klasy. Jak widzimy mamy tu klasę abstrakcyjną Car jak w poprzednim diagramie. Występują tu dwie klasy, które ją rozszerzają – podstawowe modele aut. Jest także kolejna klasa abstrakcyjna, która również będzie rozszerzać klasę Car. Klasę DecoratorComponent będą rozszerzały klasy odpowiednich dekoratorów czyli nasze dodatki do podstawowego modelu samochodu. Zwróćmy uwagę, że w rezultacie mamy znacznie mniej klas. W dodatku jeśli przyjdzie nam rozszerzyć ofertę o nowy model auta czy o nowy dodatek wystarczy, że dopiszemy odpowiednie klasy. Nie będziemy musieli zmieniać kodu klas abstrakcyjnych. Zatem nasz projekt jest otwarty na rozbudowę i zamknięty na modyfikacje. Podam implementacje klas abstrakcyjnych, RaSha2020 oraz dowolnej klasy dekorującej. Reszta odpowiednich klas jest analogiczna.

Klasa Car:

package Cars;

public abstract class Car {
    public String description = "Samochod nieznany";
    public String getDescription() {
        return description;
    }
    public abstract double cost();
}

Klasa DecoratorComponent:

package Decorators;

import Cars.Car;

public abstract class DecoratorComponent extends Car {
    public abstract String getDescription();
}

Klasa RaSha2020:

package Cars;

public class RaShaV2020 extends Car {
    public RaShaV2020(){
        description ="RaSha ver.2020";
    }
    @Override
    public double cost() {
        return 12000.00;
    }
}

Klasa AirConditioning:

package Decorators;

import Cars.Car;

public class AirConditioning extends DecoratorComponent {
    Car auto;
    public AirConditioning(Car car){
        this.auto= car;
    }
    @Override
    public String getDescription() {
        return auto.getDescription()+", klimatyzacja";
    }

    @Override
    public double cost() {
        return auto.cost()+5000.00;
    }
}

Na koniec klasa kliencka. Tu również określiłam dodatki.

import Decorators.AirConditioning;
import Decorators.MultifunctionSteeringWheel;
import Cars.RaShaV2020;
import Cars.RaShaV2021;
import Cars.Car;

public class CarMain {
    public static void main(String[] args){
        System.out.println("-------- 2021 --------");
        Car car = new RaShaV2021();
        car = new AirConditioning(car);
        System.out.println("Opis samochodu: "+ car.getDescription());
        System.out.println("koszt: "+ car.cost()+"zł");
        System.out.println();
        System.out.println("-------- 2020 --------");
        Car auto = new RaShaV2020();
        auto = new MultifunctionSteeringWheel(new AirConditioning(auto));
        System.out.println("Opis samochodu: "+auto.getDescription());
        System.out.println("koszt: "+auto.cost()+"zł");
    }
}

Jak widzimy na początku stworzyliśmy nowy obiekt typu Car – jest to jeden z podstawowych modeli samochodu. Następnie stworzony obiekt „opakowujemy” dekoratorem AirConditioning. Możemy to zrobić ponieważ nadal mamy do czynienia z obiektem typu Car. Dzięki temu rozszerzyliśmy jego funkcjonalność – zaktualizował się opis jak i cena samochodu. Co więcej raz „opakowany” obiekt możemy znów opakować. Zobaczmy rezultat wykonania:

-------- 2021 --------
Opis samochodu: RaSha ver.2021, klimatyzacja
koszt: 25000.0zł

-------- 2020 --------
Opis samochodu: RaSha ver.2020, klimatyzacja, wielofunkcyjna kierownica
koszt: 20000.0zł

Podsumowując. Wzorzec dekorator posługuje się zbiorem klas dekorujących (dekoratorów), które są wykorzystywane do dekorowania poszczególnych obiektów. Klasy dekorujące odzwierciedlają typy obiektów dekorowanych, czyli są tego samego typu co obiekty dekorowane, niezależnie czy zostało to osiągnięte metodą dziedziczenia czy implementacją odpowiednich interfejsów. Dekoratory zmieniają zachowania obiektów dekorowanych. Dodatkowo każdy składnik może być udekorowany dowolną liczbą dekoratorów. Wzorzec ten stanowi pod względem funkcjonalności alternatywę dla dziedziczenia. Należy jednak pamiętać, że zastosowanie dekoratorów może być przyczyną pojawienia się w projekcie dużej ilości małych obiektów – nadużywanie dekoratorów może doprowadzić do zdecydowanego wzrostu złożoności kodu.

pliki do pobrania:
Car Download
CarDecorator Download