通过实际示例了解“装饰器模式”


Answers:


226

装饰器模式实现了向任何对象动态添加职责的单一目标。

考虑一个比萨店的情况。在比萨店,他们将出售少量比萨饼品种,并且还将在菜单中提供浇头。现在设想一种情况,如果比萨店必须提供比萨饼和浇头的每种组合的价格。即使有四个基本的比萨饼和8个不同的浇头,应用程序也会疯狂地维护所有这些比萨饼和浇头的具体组合。

装饰器模式来了。

按照装饰器模式,您将实现装饰物的浇头,而比萨​​饼将由这些装饰物的装饰器装饰。实际上,每个客户都希望自己想要的浇头,最终的账单金额将由基础比萨和额外订购的浇头组成。每个浇头装饰者都会知道它正在装饰的比萨饼及其价格。Topping对象的GetPrice()方法将返回比萨饼和浇头的累计价格。

编辑

这是上面说明的代码示例。

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
不喜欢这种模式。也许这是一个例子。就OOD而言,我遇到的主要问题是馅料不是披萨。索要适用于披萨的比萨的价格,这并不适合我。但是,这是一个非常周到且详细的示例,因此我并不是要为此而敲你。
汤姆W 2010年

39
@TomW我认为部分问题是命名。所有“ Topping”类都应称为“ PizzaWith <Topping>”。例如,“ PizzaWithMushrooms”。
2013年

2
我认为,装饰器最好尽可能平整地使用。我的意思是说尽可能少的“装饰包装的装饰工”。所以也许这个例子不是最合适的。但这很彻底,很好。
2013年

17
从另一个角度看,这甚至不接近“现实世界”。在现实世界中,您不需要在每次需要在菜单中添加新的浇头(或更改价格)时重新编译。浇头(通常)存储在数据库中,因此使上面的示例无效。
Stelios Adamantidis 2015年

4
^这。我认为这是一直困扰我学习这种模式的原因。如果我是一家软件公司,并且写过披萨店软件,那我就不需要每次都重新编译和重新发行。我想在后端的表格中添加一行,或者很容易满足他们要求的内容。好说,@ Stelios Adamantidis。我认为模式最大的优势是在那时修改第三方类。
Canucklesandwich

33

这是一个简单的示例,将新行为动态添加到现有对象或Decorator模式中。由于动态语言(例如Javascript)的性质,该模式成为语言本身的一部分。

// 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');


简单而精确!很好的例子!
nagendra547

1
我认为装饰器模式的概念在这里不适用。实际上,这根本不是一个模式!是的,您正在运行时添加新方法。可能在一个switch或简单的内部if,您可以断言这是向类中动态添加行为的一个很好的例子。但是,我们至少需要两个类才能以这种模式定义装饰器和装饰对象。
伊曼

1
@Zich我知道我的示例中没有装饰器,但是可以通过添加充当装饰器的函数来轻松解决。但是在我的示例中有一个装饰对象。模式是否在任何地方都明确说明您需要两个
阿努拉格

18

值得注意的是,Java I / O模型基于装饰器模式。该阅读器在...之上的分层结构是装饰器的真实示例。


真实的公共API中还有其他示例吗?这是我所知道的唯一一个。
乔西亚·约德

似乎自然界中的所有包装器函数都内置了某种装饰器模式,这是我认为的吗?
哈维·林

好的例子 !!
nagendra547

8

示例-场景-假设您正在编写一个加密模块。这种加密可以使用DES-数据加密标准对透明文件进行加密。同样,在系统中,您可以将加密作为AES-高级加密标准进行。另外,您可以结合使用加密-首先是DES,然后是AES。或者,您可以先拥有AES,再拥有DES。

讨论-您将如何应对这种情况?您不能继续创建此类组合的对象-例如AES和DES-总共4种组合。因此,您需要有4个单独的对象。随着加密类型的增加,这将变得很复杂。

解决方案-在运行时不断建立堆栈-根据需要进行组合。这种堆栈方法的另一个优点是您可以轻松地将其展开。

这是解决方案-在C ++中。

首先,您需要一个基类-堆栈的基本单元。您可以将其视为堆栈的基础。在此示例中,它是清除文件。让我们始终遵循多态性。首先使该基本单元成为接口类。这样,您可以根据需要实现它。此外,在包含此基本单元时,您无需考虑依赖关系。

这是接口类-

class IclearData
{
public:

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

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

现在,实现此接口类-

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;
    }

};

现在,让我们制作一个装饰器抽象类-可以扩展为创建任何种类的样式-这里的样式是加密类型。装饰器抽象类与基类有关。因此,装饰器“是”一种接口类。因此,您需要使用继承。

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;
}

现在,让我们创建一个具体的装饰器类-加密类型-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;
    }

};

现在,假设装饰器类型为DES-

const std :: string desEncrypt =“ DES加密的”;

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;
    }

};

让我们编写一个客户端代码来使用此装饰器类-

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;
}

您将看到以下结果-

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

这是UML图-它的类表示。如果您想跳过代码,而专注于设计方面。

在此处输入图片说明


1
这个例子不是更适合strategy pattern吗?
exexzian

@exexzian是的,我的学生们一直向我提出针对此类问题的一系列策略,而且这对我来说也似乎是最干净的解决方案。
Josiah Yoder

不可以,使用策略模式不能将加密方法结合在一起。因此,您将必须为每种可能的组合创建一个策略类。
deetz

4

装饰器模式通过与该对象的其他类似子类链接来帮助您更改或配置对象的功能。

最好的示例是java.io包中的InputStream和OutputStream类

    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.

在这种情况下,调用链从ObjectOutputStream开始,一直到File类,然后File类返回值,然后其他三个子类将它们全部加起来,最后,ObjectOutputStream的方法的值返回它是正确吗?
哈维·林

3

什么是Java中的装饰器设计模式。

GoF书中的Decorator模式的正式定义(设计模式:可重用的面向对象软件的元素,1995年,Pearson Education,Inc.以Pearson Addison Wesley的身份出版)说,

“将额外的责任动态地附加到对象上。装饰器为子类提供了灵活的替代方案,以扩展功能。”

假设我们有一个披萨,我们想用诸如Masala鸡肉,洋葱和Mozzarella奶酪这样的配料来装饰它。让我们看看如何在Java中实现它...

演示如何在Java中实现Decorator Design Pattern的程序。

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

我在工作中广泛使用了Decorator模式。我在博客上发布了有关如何在日志记录中使用它的帖子


我不喜欢您只是将一个链接作为答案。但是您的Blog文章非常有帮助,我只好赞成:)。现在我真的明白了。每个人都来披萨,而您就是一个很好的例子。
尼古拉斯·拉布

2

装饰器模式使您可以动态地向对象添加行为。

让我们以一个示例为例,您需要构建一个应用程序来计算各种汉堡的价格。您需要处理各种不同的汉堡,例如“大号”或“配奶酪”,每种汉堡的价格都相对于基本汉堡。例如,为汉堡加奶酪增加10美元,为大汉堡增加15美元,等等。

在这种情况下,您可能会想创建子类来处理这些子类。我们可以用Ruby表示为:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

在上面的示例中,BurgerWithCheese类继承自Burger,并且覆盖了price方法,将$ 15添加到超类中定义的价格上。您还将创建LargeBurger类并定义相对于Burger的价格。但是,您还需要为“大”和“加奶酪”的组合定义一个新类。

现在,如果我们需要为“汉堡配薯条”服务会发生什么?我们已经有4个类来处理这些组合,并且我们将需要再添加4个类来处理3个属性的所有组合-“大”,“配奶酪”和“配薯条”。我们现在需要8节课。添加另一个属性,我们将需要16。这将增长为2 ^ n。

相反,让我们尝试定义一个接受Burger对象的BurgerDecorator:

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

在上面的示例中,我们创建了BurgerDecorator类,BurgerWithCheese类从该类继承。我们还可以通过创建LargeBurger类来表示“大型”变体。现在,我们可以在运行时将带有奶酪的大汉堡定义为:

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

还记得使用继承来添加“带有炸薯条”的变体会涉及到再添加4个子类吗?使用装饰器,我们将只创建一个新类BurgerWithFries,以处理新的变体并在运行时进行处理。每个新属性都需要更多的装饰器来覆盖所有排列。

PS。这是我写的有关在Ruby中使用Decorator Pattern的文章的简短版本,如果您想查找更详细的示例,可以阅读。


2

装饰器:

  1. 在运行时向对象添加行为。继承是实现此功能的关键,这既是此模式的优缺点。
  2. 它增强了界面的行为
  3. 可以将装饰器视为仅具有一个组件的简并复合材料。但是,Decorator会增加其他职责-并非用于对象聚合。
  4. Decorator类声明与LCD(最低类分母)接口的组成关系,并且此数据成员在其构造函数中初始化。
  5. 装饰器旨在让您无需添加子类即可向对象添加职责

有关更多详细信息,请参阅源制作文章。

Decorator(Abstract):它是一个抽象类/接口,它实现了组件接口。它包含组件接口。在没有此类的情况下,您需要针对不同组合的ConcreteDecorators的许多子类。组件的组成减少了不必要的子类。

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);;
}

看看下面关于SE问题的UML图和代码示例。

IO的装饰器模式

有用的文章:

journaldev

维基百科

Decorator模式的实词示例:VendingMachineDecorator已在@中进行了解释

何时使用装饰图案?

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

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

在上面的示例中,茶或咖啡(饮料)已用糖和柠檬装饰。




1

一段时间以前,我已经将代码库重构为使用Decorator模式,因此我将尝试解释用例。

假设我们有一组服务,并且根据用户是否已获得特定服务的许可证,我们需要启动该服务。

所有服务都有一个通用的接口

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
  }
}

如果仔细观察,ServiceSupport则取决于LicenseManager。但是为什么要依赖它LicenseManager呢?如果我们需要不需要检查许可证信息的后台服务该怎么办。在当前情况下,我们将不得不以某种方式训练LicenseManager返回true后台服务。这种方法在我看来并不好。根据我的说法,许可证检查和其他逻辑是相互正交的。

所以装饰图案可以解决,并开始使用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");

外卖

  • 代码的凝聚力更好
  • 单元测试变得更加容易,因为在测试ServiceSupport时不必模拟许可
  • 无需通过任何特殊检查来绕开许可,即可对后台服务进行任何检查
  • 适当分工

1

让我们以PubG为例。突击步枪在4倍变焦下效果最佳,当我们使用它时,我们还需要补偿器和抑制器。它将减少后坐力,并降低发射声和回声。我们将需要实现此功能,以便玩家可以购买自己喜欢的枪支及其配件。玩家可以购买枪支或某些配件或所有配件,并会相应收费。

让我们看看如何在这里应用装饰器模式:

假设有人想购买带有上述所有三个附件的SCAR-L。

  1. 拿一个SCAR-L的物体
  2. 用4倍缩放对象装饰(或添加)SCAR-L
  3. 用抑制器对象装饰SCAR-L
  4. 用压缩机对象装饰SCAR-L
  5. 调用cost方法,让每个对象代表使用附件的cost方法添加成本

这将导致这样的类图:

工作中的装饰器模式

现在,我们可以有这样的类:

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());
    }      
}

我们也可以类似地添加其他配件并装饰我们的Gun。

参考:

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


0

装饰器设计模式:此模式有助于在运行时修改对象的特征。它为物体提供了不同的风味,并提供了灵活性,可以选择我们要在该风味中使用的成分。

现实生活中的例子:假设您在飞机上有一个主舱座位。现在,您可以在座椅上选择多种便利设施。每个便利设施都有其相关的成本。现在,如果用户选择Wifi和优质食物,则需要向他/她收取座位+ wifi +优质食物的费用。

在此处输入图片说明

在这种情况下,装饰器设计模式确实可以为我们提供帮助。请访问上面的链接,以了解有关装饰器模式和一个实际示例的实现的更多信息。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.