通过一个真实世界的例子来理解“装饰模式”

时间:2010-04-25 06:36:47

标签: decorator design-patterns

我正在研究GOF中记载的装饰模式

请帮助我理解装饰模式。有人可以给出一个用例,说明它在现实世界中的用处吗?

15 个答案:

答案 0 :(得分:216)

  

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

考虑披萨店的情况。在披萨店,他们将出售少量披萨品种,他们还将在菜单中提供配料。现在设想一种情况,其中如果披萨店必须为披萨和馅料的每种组合提供价格。即使有四种基本的比萨饼和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;
    }
}

答案 1 :(得分:30)

这是一个动态添加新行为或Decorator模式的简单示例。由于Javascript等动态语言的性质,这种模式成为语言本身的一部分。

// create a message object
var message = {
    text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit..."
};

// add logging behavior to the message object dynamically
message.log = function() {
    console.log(this.text);
};

// use the newly added behavior to log text
​message.log();​ // Loren ipsum...​​​​​​​​​​​​​​​

答案 2 :(得分:16)

值得注意的是,Java i / o模型基于装饰器模式。这个阅读器在...之上的阅读器顶层是一个真正真实的装饰器例子。

答案 3 :(得分:7)

示例 - 场景 - 假设您正在编写加密模块。此加密可以使用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;
    }

};

现在,让我们制作一个装饰器抽象类 - 可以扩展为创建任何类型的口味 - 这里的味道是加密类型。此装饰器抽象类与基类相关。因此,装饰者&#34;是&#34;一种接口类。因此,您需要使用继承。

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 =&#34; DES加密&#34 ;;

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图 - 它的类表示。如果您想跳过代码并专注于设计方面。

enter image description here

答案 4 :(得分:4)

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

最好的例子是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.

答案 5 :(得分:3)

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

GoF书中的装饰模式的正式定义(设计模式:可重复使用的面向对象软件的元素,1995,Pearson Education,Inc。出版为Pearson Addison Wesley)说你可以,

&#34;动态地将附加职责附加到对象。装饰 为扩展功能提供了子类化的灵活替代方案。&#34;

让我们说我们有比萨饼,我们想要用鸡肉Masala,洋葱和莫扎里拉奶酪等配料来装饰它。让我们看看如何在Java中实现它......

演示如何在Java中实现Decorator设计模式的程序。

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

}

}

答案 6 :(得分:3)

我在工作中广泛使用了Decorator模式。我提出了post on my blog关于如何将其用于日志记录的信息。

答案 7 :(得分:2)

  

装饰器模式允许您动态地向对象添加行为。

让我们举一个例子,你需要建立一个计算不同种类汉堡价格的应用程序。您需要处理汉堡的不同变体,例如&#34; large&#34;或者&#34;与奶酪&#34;,每个都有相对于基本汉堡的价格。例如。汉堡加奶酪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的价格。但是你还需要为&#34; large&#34;的组合定义一个新类。和&#34;奶酪&#34;。

如果我们需要用薯条和汉堡一起吃汉堡会发生什么呢?我们已经有4个类来处理这些组合,我们需要再添加4个类来处理3个属性的所有组合 - &#34;大&#34;,&#34;与奶酪&#34;和&#34;用薯条&#34;。我们现在需要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类继承了该类。我们也可以代表&#34;大&#34;通过创建LargeBurger类来实现变化。现在我们可以在运行时用奶酪定义一个大汉堡:

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

记住如何使用继承来添加&#34; with fries&#34;变化将涉及添加4个子类?使用装饰器,我们只需创建一个新类BurgerWithFries来处理新的变体并在运行时处理它。每个新属性都需要更多的装饰来覆盖所有的排列。

PS。这是我撰写的关于using the Decorator Pattern in Ruby的文章的简短版本,如果您想了解更详细的示例,可以阅读。

答案 8 :(得分:2)

<强>装饰:

  1. 在运行时向对象添加行为。继承是实现此功能的关键,这是该模式的优点和缺点。
  2. 它增强了界面的行为
  3. Decorator可以被视为只有一个组件的简并 Composite 。但是,Decorator增加了额外的职责 - 它不适用于对象聚合。
  4. Decorator类声明与LCD(最低类别分母)接口的组合关系,并且此数据成员在其构造函数中初始化。
  5. Decorator旨在让您在不​​进行子类化的情况下向对象添加职责
  6. 有关详细信息,请参阅sourcemaking文章。

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

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

    请查看下面的UML图和代码示例的SE问题。

    Decorator Pattern for IO

    有用的文章:

    journaldev

    wikipedia

    装饰者模式的真实单词示例:VendingMachineDecorator 已被解释@

    When to Use the Decorator Pattern?

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

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

答案 9 :(得分:2)

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

Java I / O模型基于装饰器模式。

Java IO as decorator pattern

答案 10 :(得分:1)

维基百科上有一个关于用滚动条装饰窗口的例子:

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

这是另一个非常真实的世界&#39; &#34;团队成员,团队负责人和经理&#34;的例子,它说明了装饰者模式不可替代的简单继承:

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

答案 11 :(得分:1)

装饰器只是子类化的组合替代。 每个人都提到的有关此主题的原书中的常见示例是文本处理应用程序。

假设你写了一个段落。您将其突出显示为黄色。你把一个句子用斜体。您将斜体句子的一半加粗,下一个句子的一半也加粗。您增加斜体和粗体字母之一的字体大小。您更改了一半突出显示部分的字体样式,其中一些覆盖了斜体部分,有些没有...

所以我要问您如何实现该功能。你从一个简单的、未修饰的字母的类开始。你接下来要做什么?

我假设您不会使用子类化。您将需要如此复杂、错综复杂的多重继承层次结构来实现我所描述的所有组合以及更多,而子类化和多重继承将是荒谬的。我认为这不需要解释。

您可能会建议将所有这些属性打包到您的信件对象中。用于定义字体样式、大小、突出显示、粗体、斜体的属性,不胜枚举。您可以添加到字母对象的每种属性,您的字母类中都有一个属性。

那么这种基于属性的方法有什么问题?

  1. 现在你的类很臃肿,占用了大量内存。它具有与之相关的所有这些不必要的属性,其中大部分是它永远不会使用的。大多数字母只是……字母。未装饰。
  2. 你的字母类的数据正在以一种完全公开的方式被使用,你的类只是一个美化的结构。对于所有这些属性,您有一堆 getter 和 setter。外部代码访问这些 setter 并修改对象的图形外观。您的对象与外部代码之间存在紧密耦合。
  3. 一切都集中在一个地方,它不是模块化的。它只是一个臃肿的、相互关联的代码包。在处理您的信件对象的外部代码中也是如此。

从根本上说,这是一个面向对象设计、正确封装和关注点分离的问题。

现在,让我们理所当然地想使用更好的 OO 设计原则。我们要使用封装,我们要保持外部代码和我们的字母类之间的松散耦合。我们想最小化我们的字母对象内存占用。如何...?我们不能使用子类...

所以我们使用装饰器,它是面向对象设计的一种组合方法——它与自顶向下的子类化方法相反。您可以在运行时使用更多功能包装这些字母对象,并在它们之上构建。

这就是装饰器模式 - 它是子类化的组合替代方案。在我们的示例中,您向需要突出显示的字母对象添加了一个装饰器。您可以以任意数量的方式组合任意数量的装饰器,并将它们全部包裹在给定的字母周围。装饰器接口总是透明的,所以你从外面看这些字母还是一样的。

任何时候您需要以任意和可重组的方式增强功能,都可以考虑这种方法。多重继承会遇到各种各样的问题,只是不可扩展。

答案 12 :(得分:0)

Decorator Design Pattern: 此模式有助于在运行时修改对象的特征。它为物体提供不同的口味,并可灵活选择我们想要用于那种口味的成分。

真实生活示例: 假设你在飞行中有一个主舱座位。现在,您可以选择座位上的多种设施。每个便利设施都有自己的成本。现在,如果用户选择Wifi和优质食品,他/她将被收取座位+ wifi +优质食品的费用。

enter image description here

在这种情况下,装饰设计模式可以真正帮助我们。访问上面的链接,了解更多关于装饰器模式和实现一个现实生活中的例子。

答案 13 :(得分:0)

很久以前,我已经将代码库重构为使用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时不必模拟许可
  • 无需通过后台服务的任何特殊检查来绕开许可
  • 正确划分职责

答案 14 :(得分:0)

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

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

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

  1. 带一个SCAR-L对象
  2. 用4倍缩放对象装饰(或添加)SCAR-L
  3. 用抑制器对象装饰SCAR-L
  4. 用压缩机对象装饰SCAR-L
  5. 调用cost方法,让每个对象委托增加成本 使用配件成本法

这将导致这样的类图:

Decorator pattern at work

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

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

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

参考:

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