为啥我们需要装饰器设计模式中的装饰器?

Posted

技术标签:

【中文标题】为啥我们需要装饰器设计模式中的装饰器?【英文标题】:Why do we need the decorator in the decorator design pattern?为什么我们需要装饰器设计模式中的装饰器? 【发布时间】:2010-09-21 09:31:57 【问题描述】:

假设我有一个名为A 的类,并且我想使用装饰器设计模式。如果我错了,请纠正我,但要让它工作,我们需要创建一个装饰器类,比如ADecorator,它将包含对A 实例的引用,并且所有其他装饰器都将其扩展为添加功能。

我不明白为什么我们必须创建一个装饰器类,而不是使用A 实例?

【问题讨论】:

【参考方案1】:

装饰器模式用于动态(即在运行时)向对象添加功能。通常,当您编写类时,对象的功能会固定。但重要的一点是,对象的功能以对对象客户端透明的方式进行扩展,因为它实现了与原始对象将责任委托给装饰对象相同的接口。

装饰器模式适用于对象可能具有许多可选功能的场景。如果没有装饰器模式,您将不得不为每个对象选项配置创建不同的类。一个非常有用的例子来自 O'Reilly 的 Head First Design Patterns 一书。它使用了一个听起来像星巴克的咖啡店示例。

因此,您可以使用成本等方法获得基本咖啡。

public double cost()
     return 3.45;

然后客户可以添加价格为 0.35 的奶油,因此您现在使用 cost 方法创建一个 CoffeeCream 类:

public double cost()
    return 3.80;

那么客户可能想要价格为 0.5 的摩卡,他们可能想要加奶油的摩卡或不加奶油的摩卡。因此,您创建类 CoffeeMochaCream 和 CoffeeMocha。然后一个客户想要双份奶油,所以你创建了一个类 CoffeeCreamCream……等等。你最终得到的是类爆炸。请原谅使用的糟糕示例。有点晚了,我知道这是微不足道的,但它确实表达了这一点。

相反,您可以使用抽象成本方法创建 Item 抽象类:

public abstract class Item
    public abstract double cost();

您可以创建一个扩展 Item 的具体 Coffee 类:

public class Coffee extends Item
    public double cost()
       return 3.45;
    

然后你创建一个扩展相同接口并包含一个 Item 的 CoffeeDecorator。

public abstract class CoffeeDecorator extends Item
     private Item item;
     ...

然后您可以为每个选项创建具体的装饰器:

public class Mocha extends CoffeeDecorator

   public double cost()
     return item.cost() + 0.5;
   


请注意装饰器是如何不关心它包装的对象类型,只要它是一个项目?它使用 item 对象的 cost() 并简单地添加自己的成本。

public class Cream extends CoffeeDecorator

   public double cost()
     return item.cost() + 0.35;
   


现在可以使用以下几个类进行大量配置: 例如

 Item drink = new Cream(new Mocha(new Coffee))); //Mocha with cream

 Item drink = new Cream(new Mocha(new Cream(new Coffee))));//Mocha with double cream

等等。

【讨论】:

我发现这个答案比***的文章更容易理解。 @dhiller 是的。设计模式是我尝试的第一个,并开始向我所有的学生推荐它们。 这是一个更容易理解的示例,尽管我认为这不是应该使用这种模式解决的正确问题。【参考方案2】:

顺便说一句,如果您刚刚开始研究模式,那么 Head First Design Patterns 这本书是非凡的。它确实使概念易于理解,并确保以非常容易理解的方式对比和比较相似的模式。

【讨论】:

我必须支持这一点,这本书令人难以置信。事实上,上面的咖啡示例取自那本书,但该书更进一步。【参考方案3】:

在某些语言(如 Ruby 或 javascript)中,您可以向 A 实例添加新功能。我注意到您的问题被标记为 Java,所以我假设您在问为什么不能在 Java 中执行此操作。原因是Java是静态类型的。 A 实例只能具有类 A 定义或继承的方法。因此,如果您想在运行时为 A 实例提供 A 未定义的方法,则必须在不同的类中定义此新方法。

【讨论】:

以上是关于为啥我们需要装饰器设计模式中的装饰器?的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之-装饰器模式

装饰器详解

为啥装饰器模式适用于指针而不适用于引用?

设计模式之装饰器模式

PHP设计模式之装饰器模式

设计模式 —— 装饰器模式(Decorator Pattern)