Zrozum „Wzorzec dekoratora” na przykładzie z prawdziwego świata


167

Uczyłem się Wzorzec Dekorator co zostało udokumentowane w GOF .

Proszę, pomóż mi zrozumieć Wzorzec Dekoratora . Czy ktoś mógłby podać przykład użycia, w którym jest to przydatne w prawdziwym świecie?


8
Możesz znaleźć tutaj kilka przykładów realworld w Java API: stackoverflow.com/questions/1673841/ ...
BalusC Kwietnia

Artykuł przedstawiający zalety wzoru dekoratora z prostymi przykładami: dzone.com/articles/is-inheritance-dead
nbilal

Odpowiedzi:


226

Wzór dekoratora osiąga jeden cel, jakim jest dynamiczne dodawanie obowiązków do dowolnego obiektu.

Rozważmy przykład pizzerii. W pizzerii sprzedadzą kilka odmian pizzy, a także zapewnią dodatki do menu. Teraz wyobraź sobie sytuację, w której pizzeria musi podawać ceny za każdą kombinację pizzy i dodatków. Nawet jeśli są cztery podstawowe pizze i 8 różnych dodatków, aplikacja oszalałaby, zachowując wszystkie te konkretne kombinacje pizzy i dodatków.

Oto wzór dekoratora.

Zgodnie ze wzorem dekoratora, zastosujesz dodatki, ponieważ dekoratorzy i pizze będą dekorowane przez dekoratorów tych dodatków. Praktycznie każdy klient chciałby mieć dodatki według własnego uznania, a ostateczna kwota rachunku składać się będzie z podstawowej pizzy i dodatkowo zamówionych dodatków. Każdy dekorator dodatków wiedziałby o pizzy, którą ozdabia i jaka jest cena. Metoda GetPrice () obiektu Topping zwróciłaby skumulowaną cenę zarówno pizzy, jak i dodatku.

EDYTOWAĆ

Oto przykład kodu wyjaśnienia powyżej.

public abstract class BasePizza
{
    protected double myPrice;

    public virtual double GetPrice()
    {
        return this.myPrice;
    }
}

public abstract class ToppingsDecorator : BasePizza
{
    protected BasePizza pizza;
    public ToppingsDecorator(BasePizza pizzaToDecorate)
    {
        this.pizza = pizzaToDecorate;
    }

    public override double GetPrice()
    {
        return (this.pizza.GetPrice() + this.myPrice);
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //Client-code
        Margherita pizza = new Margherita();
        Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());

        ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
        ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());

        MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());

        JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());

        Console.ReadLine();
    }
}

public class Margherita : BasePizza
{
    public Margherita()
    {
        this.myPrice = 6.99;
    }
}

public class Gourmet : BasePizza
{
    public Gourmet()
    {
        this.myPrice = 7.49;
    }
}

public class ExtraCheeseTopping : ToppingsDecorator
{
    public ExtraCheeseTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 0.99;
    }
}

public class MushroomTopping : ToppingsDecorator
{
    public MushroomTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

public class JalapenoTopping : ToppingsDecorator
{
    public JalapenoTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

104
Nie podoba mi się ten wzór ani trochę. Może to jednak przykład. Główny problem, jaki mam z tym, jeśli chodzi o OOD, polega na tym, że polewa nie jest pizzą . Pytanie o cenę pizzy, na którą jest nałożony, po prostu mi nie odpowiada. Jest to jednak bardzo przemyślany i szczegółowy przykład, więc nie chcę cię za to pukać.
Tom W

39
@TomW Myślę, że częścią problemu jest nazewnictwo. Wszystkie klasy „Topping” powinny mieć nazwę „PizzaWith <Topping>”. Na przykład „Pizza z grzybami”.
Josh Noe

2
Moim zdaniem dekoratorów najlepiej używać tak płasko, jak to tylko możliwe. Rozumiem przez to jak najmniej „dekoratorów pakujących dekoratorów”. Więc może ten przykład nie jest najbardziej trafny. Ale jest dość dokładny, co jest miłe.
thekingoftruth

17
Z innej perspektywy nie jest to nawet bliskie „prawdziwemu światu”. W prawdziwym świecie nie należy rekompilować za każdym razem, gdy trzeba dodać nowy dodatek w menu (lub zmienić cenę). Polewy są (zwykle) przechowywane w bazie danych, co powoduje, że powyższy przykład jest bezużyteczny.
Stelios Adamantidis

4
^ To. Myślę, że to właśnie przeszkadzało mi przez cały czas studiowania tego wzoru. Gdybym był firmą programistyczną i napisałbym oprogramowanie dla pizzerii, nie chciałbym za każdym razem rekompilować i ponownie przesyłać. Chciałbym dodać wiersz w tabeli na zapleczu lub coś, co z łatwością zadba o ich wymagania. Dobrze powiedziane, @Stelios Adamantidis. Myślę, że największą siłą wzorców byłaby wtedy modyfikacja klas zewnętrznych.
Canucklesandwich

33

To jest prosty przykład dynamicznego dodawania nowego zachowania do istniejącego obiektu lub wzorca dekoratora. Ze względu na naturę języków dynamicznych, takich jak JavaScript, wzorzec ten staje się częścią samego języka.

// Person object that we will be decorating with logging capability
var person = {
  name: "Foo",
  city: "Bar"
};

// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
  object.log = function(property) {
    console.log(this[property]);
  }
}

// Person is given the dynamic responsibility here
MakeLoggable(person);

// Using the newly added functionality
person.log('name');


Proste i precyzyjne! Świetny przykład!
nagendra547

1
Nie wydaje mi się, aby koncepcja wzorca dekoratora miała tutaj zastosowanie. Właściwie to wcale nie jest wzór !. Tak, dodajesz nową metodę w czasie wykonywania. I prawdopodobnie wewnątrz a switchlub simple if, można by powiedzieć, że jest to świetny przykład dynamicznego dodawania zachowania do klasy, ale potrzebujemy co najmniej dwóch klas, aby zdefiniować dekorator i dekorowane obiekty w tym wzorze.
Iman

1
@Zich Rozumiem, że w moim przykładzie nie ma dekoratora, ale łatwo to naprawić, dodając funkcję, która służy jako dekorator. Ale w moim przykładzie jest ozdobiony przedmiot. Czy wzór mówi gdzieś, że potrzebujesz konkretnie dwóch klas ?
Anurag

18

Warto zauważyć, że model we / wy Java jest oparty na wzorcu dekoratora. Warstwowanie tego czytnika na tym czytniku na wierzchu ... jest naprawdę prawdziwym przykładem dekoratora.


Czy są jakieś inne przykłady w prawdziwych publicznych interfejsach API? To jedyny, jaki znam.
Josiah Yoder,

Wygląda na to, że wszystkie funkcje opakowania w naturze mają wbudowany jakiś wzór dekoratora, czy to jest to, o czym myślę?
Harvey Lin

Dobry przykład !!
nagendra547

8

Przykład - scenariusz - powiedzmy, że piszesz moduł szyfrowania. To szyfrowanie może zaszyfrować czysty plik przy użyciu standardu szyfrowania DES - Data. Podobnie w systemie możesz mieć szyfrowanie w standardzie szyfrowania AES - Advance. Możesz także mieć kombinację szyfrowania - najpierw DES, a następnie AES. Lub możesz mieć najpierw AES, a następnie DES.

Dyskusja - Jak poradzisz sobie w tej sytuacji? Nie możesz dalej tworzyć obiektu takich kombinacji - na przykład - AES i DES - łącznie 4 kombinacje. Dlatego musisz mieć 4 oddzielne obiekty. Stanie się to skomplikowane wraz ze wzrostem typu szyfrowania.

Rozwiązanie - Kontynuuj tworzenie stosu - kombinacje w zależności od potrzeb - w czasie wykonywania. Kolejną zaletą tego podejścia do stosu jest to, że można go łatwo rozwinąć.

Oto rozwiązanie - w C ++.

Po pierwsze, potrzebujesz klasy bazowej - podstawowej jednostki stosu. Możesz myśleć jako podstawa stosu. W tym przykładzie jest to czysty plik. Postępujmy zawsze zgodnie z polimorfizmem. Najpierw utwórz klasę interfejsu tej podstawowej jednostki. W ten sposób możesz zaimplementować to, jak chcesz. Nie musisz też myśleć o zależności, włączając tę ​​podstawową jednostkę.

Oto klasa interfejsu -

class IclearData
{
public:

    virtual std::string getData() = 0;
    virtual ~IclearData() = 0;
};

IclearData::~IclearData()
{
    std::cout<<"Destructor called of IclearData"<<std::endl;
}

Teraz zaimplementuj tę klasę interfejsu -

class clearData:public IclearData
{
private:

    std::string m_data;

    clearData();

    void setData(std::string data)
        {
            m_data = data;
        }

public:

    std::string getData()
    {
        return m_data;
    }

    clearData(std::string data)
    {
        setData(data);
    }

    ~clearData()
    {
        std::cout<<"Destructor of clear Data Invoked"<<std::endl;
    }

};

Teraz stwórzmy abstrakcyjną klasę dekoratora - którą można rozszerzyć, aby stworzyć dowolny rodzaj smaków - tutaj smak jest typem szyfrowania. Ta abstrakcyjna klasa dekoratora jest powiązana z klasą bazową. Zatem dekorator „jest” rodzajem klasy interfejsu. Dlatego musisz użyć dziedziczenia.

class encryptionDecorator: public IclearData
{

protected:
    IclearData *p_mclearData;

    encryptionDecorator()
    {
      std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
    }

public:

    std::string getData()
    {
        return p_mclearData->getData();
    }

    encryptionDecorator(IclearData *clearData)
    {
        p_mclearData = clearData;
    }

    virtual std::string showDecryptedData() = 0;

    virtual ~encryptionDecorator() = 0;

};

encryptionDecorator::~encryptionDecorator()
{
    std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

Teraz zróbmy konkretną klasę dekoratora - Typ szyfrowania - AES -

const std::string aesEncrypt = "AES Encrypted ";

class aes: public encryptionDecorator
{

private:

    std::string m_aesData;

    aes();

public:

    aes(IclearData *pClearData): m_aesData(aesEncrypt)
    {
        p_mclearData = pClearData;
        m_aesData.append(p_mclearData->getData());
    }

    std::string getData()
        {
            return m_aesData;
        }

    std::string showDecryptedData(void)
    {
        m_aesData.erase(0,m_aesData.length());
        return m_aesData;
    }

};

Powiedzmy teraz, że typ dekoratora to DES -

const std :: string desEncrypt = "DES Encrypted";

class des: public encryptionDecorator
{

private:

    std::string m_desData;

    des();

public:

    des(IclearData *pClearData): m_desData(desEncrypt)
    {
        p_mclearData = pClearData;
        m_desData.append(p_mclearData->getData());
    }

    std::string getData(void)
        {
            return m_desData;
        }

    std::string showDecryptedData(void)
    {
        m_desData.erase(0,desEncrypt.length());
        return m_desData;
    }

};

Zróbmy kod klienta, który będzie używał tej klasy dekoratora -

int main()
{
    IclearData *pData = new clearData("HELLO_CLEAR_DATA");

    std::cout<<pData->getData()<<std::endl;


    encryptionDecorator *pAesData = new aes(pData);

    std::cout<<pAesData->getData()<<std::endl;

    encryptionDecorator *pDesData = new des(pAesData);

    std::cout<<pDesData->getData()<<std::endl;

    /** unwind the decorator stack ***/
    std::cout<<pDesData->showDecryptedData()<<std::endl;

    delete pDesData;
    delete pAesData;
    delete pData;

    return 0;
}

Zobaczysz następujące wyniki -

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

Oto diagram UML - reprezentacja klasy. W przypadku, gdy chcesz pominąć kod i skupić się na aspekcie projektowym.

wprowadź opis obrazu tutaj


1
czy ten przykład nie jest bardziej odpowiedni strategy pattern?
exexzian

@exexzian Tak, moi uczniowie konsekwentnie sugerują mi listę strategii dla tego typu problemu, i dla mnie jest to najczystsze rozwiązanie.
Josiah Yoder,

Nie, z wzorcem strategii nie można łączyć metod szyfrowania. W związku z tym musiałbyś stworzyć klasę strategii dla każdej możliwej kombinacji.
deetz

4

Wzorzec dekoratora pomaga zmienić lub skonfigurować funkcjonalność obiektu przez tworzenie łańcuchów z innymi podobnymi podklasami tego obiektu.

Najlepszym przykładem mogą być klasy InputStream i OutputStream w pakiecie java.io

    File file=new File("target","test.txt");
    FileOutputStream fos=new FileOutputStream(file);
    BufferedOutputStream bos=new BufferedOutputStream(fos);
    ObjectOutputStream oos=new ObjectOutputStream(bos);


    oos.write(5);
    oos.writeBoolean(true);
    oos.writeBytes("decorator pattern was here.");


//... then close the streams of course.

W tym przypadku łańcuch wywołań zaczyna się od ObjectOutputStream, a następnie przechodzi aż do klasy File, następnie klasa File zwraca wartość, następnie pozostałe trzy podklasy dodają je wszystkie i na końcu zwraca wartość metody ObjectOutputStream, to jest Zgadza się?
Harvey Lin

3

Co to jest wzorzec projektowy dekoratora w języku Java.

Formalna definicja wzorca dekoratora z książki GoF (Design Patterns: Elements of Reusable Object-Oriented Software, 1995, Pearson Education, Inc. Publishing as Pearson Addison Wesley) mówi, że możesz,

„Dynamicznie przypisuj dodatkowe obowiązki do obiektu. Dekoratory stanowią elastyczną alternatywę dla tworzenia podklas w celu rozszerzenia funkcjonalności”.

Powiedzmy, że mamy pizzę i chcemy ją ozdobić dodatkami, takimi jak kurczak masala, cebula i ser mozzarella. Zobaczmy, jak zaimplementować to w Javie ...

Program do demonstracji sposobu implementacji wzorca projektowego dekoratora w języku Java.

Pizza.java:

<!-- language-all: lang-html -->

package com.hubberspot.designpattern.structural.decorator;

public class Pizza {

public Pizza() {

}

public String description(){
    return "Pizza";
}

}



package com.hubberspot.designpattern.structural.decorator;

public abstract class PizzaToppings extends Pizza {

public abstract String description();

}

package com.hubberspot.designpattern.structural.decorator;

public class ChickenMasala extends PizzaToppings {

private Pizza pizza;

public ChickenMasala(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + " with chicken masala, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class MozzarellaCheese extends PizzaToppings {

private Pizza pizza;

public MozzarellaCheese(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "and mozzarella cheese.";
}
}



package com.hubberspot.designpattern.structural.decorator;

public class Onion extends PizzaToppings {

private Pizza pizza;

public Onion(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "onions, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class TestDecorator {

public static void main(String[] args) {

    Pizza pizza = new Pizza();

    pizza = new ChickenMasala(pizza);
    pizza = new Onion(pizza);
    pizza = new MozzarellaCheese(pizza);

    System.out.println("You're getting " + pizza.description());

}

}

3

W mojej pracy intensywnie używałem wzoru dekoratora. Zrobiłem post na moim blogu o tym, jak używać go z logowaniem.


Nie podoba mi się, że po prostu podałeś link jako odpowiedź. Ale Twój artykuł na blogu jest tak pomocny, że po prostu musiałem zagłosować :). Teraz naprawdę to rozumiem. Wszyscy przychodzą z pizzą, a ty masz doskonały przykład.
Niklas Raab

2

Wzorzec dekoratora pozwala dynamicznie dodawać zachowanie do obiektów.

Weźmy przykład, w którym musisz zbudować aplikację, która oblicza ceny różnych rodzajów hamburgerów. Musisz poradzić sobie z różnymi wariantami burgerów, takimi jak „duże” lub „z serem”, z których każdy ma cenę w stosunku do podstawowego burgera. Np. Dodaj 10 $ za burgera z serem, dodaj dodatkowe 15 $ za dużego burgera itp.

W takim przypadku możesz ulec pokusie tworzenia podklas do ich obsługi. Możemy to wyrazić w Rubim jako:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

W powyższym przykładzie klasa BurgerWithCheese dziedziczy po Burgerze i zastępuje metodę ceny, dodając 15 USD do ceny zdefiniowanej w superklasie. Utworzyłbyś również klasę LargeBurger i zdefiniowałbyś cenę w stosunku do Burgera. Ale musisz także zdefiniować nową klasę dla kombinacji „duże” i „z serem”.

A co się stanie, jeśli będziemy musieli podać „burgera z frytkami”? Mamy już 4 klasy do obsługi tych kombinacji i będziemy musieli dodać 4 kolejne, aby obsłużyć wszystkie kombinacje 3 właściwości - „duże”, „z serem” i „z frytkami”. Potrzebujemy teraz 8 klas. Dodaj kolejną właściwość, a będziemy potrzebować 16. To będzie rosło jako 2 ^ n.

Zamiast tego spróbujmy zdefiniować BurgerDecorator, który przyjmuje obiekt Burger:

class BurgerDecorator
  def initialize(burger)
    self.burger = burger
  end
end

class BurgerWithCheese < BurgerDecorator
  def price
    self.burger.price + 15
  end
end

burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

W powyższym przykładzie utworzyliśmy klasę BurgerDecorator, z której dziedziczy klasa BurgerWithCheese. Możemy również przedstawić „dużą” odmianę, tworząc klasę LargeBurger. Teraz możemy zdefiniować dużego burgera z serem w czasie wykonywania jako:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

Pamiętasz, jak użycie dziedziczenia w celu dodania odmiany „z frytkami” wymagałoby dodania 4 kolejnych podklas? W przypadku dekoratorów utworzylibyśmy po prostu jedną nową klasę, BurgerWithFries, do obsługi nowej odmiany i obsługi tego w czasie wykonywania. Każda nowa nieruchomość wymagałaby więcej dekoratora, aby pokryć wszystkie permutacje.

PS. To jest krótka wersja artykułu, który napisałem o używaniu wzoru dekoratora w języku Ruby , który możesz przeczytać, jeśli chcesz poznać bardziej szczegółowe przykłady.


2

Dekorator:

  1. Dodaj zachowanie do obiektu w czasie wykonywania . Dziedziczenie jest kluczem do osiągnięcia tej funkcjonalności, co jest zarówno zaletą, jak i wadą tego wzoru.
  2. Poprawia zachowanie interfejsu.
  3. Dekorator może być postrzegany jako zdegenerowany kompozyt zawierający tylko jeden składnik. Jednak dekorator dodaje dodatkowe obowiązki - nie jest przeznaczony do agregacji obiektów.
  4. Klasa Decorator deklaruje relację kompozycji z interfejsem LCD (najniższy mianownik klasy), a ten element członkowski danych jest inicjowany w swoim konstruktorze.
  5. Dekorator został zaprojektowany, aby umożliwić dodawanie obowiązków do obiektów bez tworzenia podklas

Odnosić się do znaleźć w artykule o wytwarzaniu źródeł .

Dekorator (abstrakcyjny) : jest to abstrakcyjna klasa / interfejs, który implementuje interfejs komponentu. Zawiera interfejs komponentowy. W przypadku braku tej klasy, potrzebujesz wielu podklas ConcreteDecorators dla różnych kombinacji. Kompozycja komponentu redukuje niepotrzebne podklasy.

Przykład JDK:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

Spójrz na poniższe pytanie SE dotyczące diagramu UML i przykładów kodu.

Wzorzec dekoratora dla IO

Przydatne artykuły:

journaldev

wikipedia

Prawdziwy przykład wzoru dekoratora: VendingMachineDecorator został wyjaśniony @

Kiedy używać wzoru dekoratora?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();

beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

W powyższym przykładzie herbata lub kawa (napój) została ozdobiona cukrem i cytryną.


2

Wzór dekoratora osiąga jeden cel, jakim jest dynamiczne dodawanie obowiązków do dowolnego obiektu .

Model we / wy Java jest oparty na wzorcu dekoratora.

Java IO jako wzorzec dekoratora


1

W Wikipedii jest przykład ozdabiania okna paskiem przewijania:

http://en.wikipedia.org/wiki/Decorator_pattern

Oto kolejny przykład „członka zespołu, kierownika zespołu i menedżera” w bardzo „prawdziwym świecie”, który ilustruje, że wzór dekoratora jest niezastąpiony przy prostym dziedziczeniu:

https://zishanbilal.wordpress.com/2011/04/28/design-patterns-by-examples-decorator-pattern/


Ten link do Zishana Bilala jest świetny - najlepszy przykład, jaki widziałem
stonedauwg

1

Jakiś czas temu refaktoryzowałem kod źródłowy do użycia wzorca dekoratora, więc spróbuję wyjaśnić przypadek użycia.

Załóżmy, że mamy zestaw usług i na podstawie tego, czy użytkownik nabył licencję na daną usługę, musimy uruchomić usługę.

Wszystkie usługi mają wspólny interfejs

interface Service {
  String serviceId();
  void init() throws Exception;
  void start() throws Exception;
  void stop() throws Exception;
}

Wstępna refaktoryzacja

abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId, LicenseManager licenseManager) {
    // assign instance variables
  }

  @Override
  public void init() throws Exception {
    if (!licenseManager.isLicenseValid(serviceId)) {
       throw new Exception("License not valid for service");
    }
    // Service initialization logic
  }
}

Jeśli uważnie obserwujesz, ServiceSupportjest zależny od LicenseManager. Ale dlaczego miałoby to zależeć LicenseManager? A gdybyśmy potrzebowali usługi w tle, która nie musi sprawdzać informacji o licencji. W obecnej sytuacji będziemy musieli jakoś trenować, LicenseManageraby wrócićtrue do usług w tle. To podejście nie wydawało mi się dobre. Według mnie sprawdzanie licencji i inne logiki były względem siebie ortogonalne.

Tak więc Decorator Pattern przychodzi na ratunek i tutaj zaczyna się refaktoryzacja z TDD.

Post refaktoryzacja

class LicensedService implements Service {
  private Service service;
  public LicensedService(LicenseManager licenseManager, Service service) {
    this.service = service;
  }

  @Override
  public void init() {
    if (!licenseManager.isLicenseValid(service.serviceId())) {
      throw new Exception("License is invalid for service " + service.serviceId());
    }
    // Delegate init to decorated service
    service.init();
  }

  // override other methods according to requirement
}

// Not concerned with licensing any more :)
abstract class ServiceSupport implements Service {
  public ServiceSupport(String serviceId) {
    // assign variables
  }

  @Override
  public void init() {
    // Service initialization logic
  }
}

// The services which need license protection can be decorated with a Licensed service
Service aLicensedService = new LicensedService(new Service1("Service1"), licenseManager);
// Services which don't need license can be created without one and there is no need to pass license related information
Service aBackgroundService = new BackgroundService1("BG-1");

Dania na wynos

  • Poprawiła się spójność kodu
  • Testowanie jednostkowe stało się łatwiejsze, ponieważ podczas testowania ServiceSupport nie trzeba symulować licencji
  • Nie musisz omijać licencjonowania przez specjalne kontrole usług działających w tle
  • Właściwy podział obowiązków

1

Weźmy na przykład PubG. Karabiny szturmowe działają najlepiej z 4-krotnym zoomem, a skoro już przy nim jesteśmy, potrzebowalibyśmy też kompensatora i tłumika. Zmniejszy to odrzut i dźwięk wystrzału oraz echo. Będziemy musieli zaimplementować tę funkcję, aby umożliwić graczom zakup ulubionej broni i akcesoriów. Gracze mogą kupić broń lub niektóre z akcesoriów lub wszystkie akcesoria i zostaną odpowiednio obciążeni.

Zobaczmy, jak jest stosowany wzór dekoratora:

Załóżmy, że ktoś chce kupić SCAR-L ze wszystkimi trzema wymienionymi powyżej akcesoriami.

  1. Weź obiekt SCAR-L
  2. Udekoruj (lub dodaj) SCAR-L z 4-krotnym powiększeniem obiektu
  3. Udekoruj SCAR-L obiektem tłumiącym
  4. Udekoruj SCAR-L przedmiotem kompresora
  5. Wywołaj metodę kosztową i pozwól delegatowi każdego obiektu dodać koszt za pomocą metody kosztowej akcesoriów

Doprowadzi to do następującego diagramu klas:

Wzór dekoratora w pracy

Teraz możemy mieć takie zajęcia:

public abstract class Gun {     
    private Double cost;    
    public Double getCost() {           
        return cost;        
       }    
    }

public abstract class GunAccessories extends Gun {  }

public class Scarl extends Gun {    
    public Scarl() {            
        cost = 100;
        }   
     }

public class Suppressor extends GunAccessories {        
    Gun gun;        
    public Suppressor(Gun gun) {            
    cost = 5;           
    this.gun = gun;     
    }               
    public double getCost(){            
        return cost + gun.getCost();
    }
}

public class GunShop{   
    public static void main(String args[]){         
    Gun scarl = new Scarl();                
    scarl = new Supressor(scarl);
    System.out.println("Price is "+scarl.getCost());
    }      
}

Podobnie możemy dodać inne akcesoria i ozdobić naszą broń.

Odniesienie:

https://nulpointerexception.com/2019/05/05/a-beginner-guide-to-decorator-pattern/


0

Wzorzec projektowy dekoratora : ten wzorzec pomaga modyfikować właściwości obiektu w czasie wykonywania. Zapewnia różne smaki przedmiotowi i daje elastyczność w wyborze składników, których chcemy użyć w tym smaku.

Przykład z życia wzięty: Załóżmy, że podczas lotu masz główne miejsce w kabinie. Teraz możesz wybrać wiele udogodnień z miejscem do siedzenia. Każde udogodnienie ma swój własny koszt. Teraz, jeśli użytkownik wybierze Wi-Fi i jedzenie premium, zostanie obciążony opłatą za miejsce + Wi-Fi + jedzenie premium.

wprowadź opis obrazu tutaj

W tym przypadku wzór projektowy dekoratora może nam naprawdę pomóc. Odwiedź powyższy link, aby dowiedzieć się więcej o wzorach dekoratorów i wdrożeniu jednego przykładu z życia.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.