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?
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?
Odpowiedzi:
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.
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;
}
}
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');
switch
lub 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.
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.
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.
strategy pattern
?
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.
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());
}
}
W mojej pracy intensywnie używałem wzoru dekoratora. Zrobiłem post na moim blogu o tym, jak używać go z logowaniem.
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.
Dekorator:
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.
Przydatne artykuły:
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ą.
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.
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/
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;
}
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, ServiceSupport
jest 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ć, LicenseManager
aby 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.
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");
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.
Doprowadzi to do następującego diagramu klas:
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/
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.
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.