何时使用装饰器模式?

时间:2009-10-11 03:11:50

标签: design-patterns decorator

我正在研究我的设计模式,而我在编码中尚未认真使用的一种模式是装饰模式。

我理解这种模式,但我想知道的是现实世界中一些具体的例子,装饰者模式是最佳/最佳/优雅的解决方案。需要装饰器模式的特定情况非常方便。

感谢。

10 个答案:

答案 0 :(得分:67)

装饰器模式用于向特定对象添加附加功能,而不是一类对象。通过子类化对象很容易为整个对象类添加功能,但是不可能以这种方式扩展单个对象。使用Decorator Pattern,您可以向单个对象添加功能,并保留其他类似对象不受修改。

在Java中,装饰器模式的经典示例是Java I / O Streams实现。

FileReader       frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);

上述代码创建了一个阅读器 - lrdr - 从文件中读取并跟踪行号。第1行创建文件阅读器(frdr),第2行添加行号跟踪。

实际上,我强烈建议您查看Java I / O类的Java源代码。

答案 1 :(得分:36)

装饰器模式与流使用很多:您可以使用流包装流以获得添加的功能。我已经在.Net框架中看到了这一点 - 据我所知,这发生在其他地方。我最喜欢的是在FileStream周围使用GZipStream,以增加压缩。

答案 2 :(得分:26)

我最近在使用以下CommandProcessor接口的Web服务中使用了装饰器模式:

public Command receive(Request request);
public Response execute(Command command);
public void respond(Response response);

基本上,CommandProcessor接收请求并创建适当的命令,执行命令并创建适当的响应,并发送响应。当我想添加时序并记录它时,我创建了一个使用现有CommandProcessor作为其组件的TimerDecorator。 TimerDecorator实现了CommandProcessor接口,但只是增加了时序,然后调用了它的目标,即真正的CommandProcessor。像这样:

public class TimerDecorator implements CommandProcessor {
   private CommandProcessor target;
   private Timer timer;

   public TimerDecorator(CommandProcessor processor) {
      this.target = processor;
      this.timer = new Timer();
   }

   public Command receive(Request request) {
      this.timer.start();
      return this.target.receive(request);
   }

   public Response execute(Command command) {
      return this.target.execute(command);
   }

   public void respond(Response response) {
      this.target.response(response);
      this.timer.stop();
      // log timer
   }

}

因此真正的CommandProcessor包含在TimerDecorator中,我可以像处理目标CommandProcessor一样处理TimerDecorator,但现在已经添加了时序逻辑。

答案 3 :(得分:8)

Decorator模式在运行时动态更改对象的功能,而不会影响对象的现有功能。

主要用例:

  1. 动态添加其他功能/职责
  2. 动态删除功能/职责
  3. 避免过多的子类化来增加额外的职责。
  4. <强>缺点:

    1. 过度使用开放封闭原则(打开扩展,关闭以进行修改)。在代码最不可能更改的地方谨慎使用此功能。
    2. 太多小班,会添加维护费用
    3. 一个真实世界的例子:计算饮料的价格,这可能包含多种口味。

      abstract class Beverage {
          protected String name;
          protected int price;
          public Beverage(){
      
          }
          public  Beverage(String name){
              this.name = name;
          }
          public void setName(String name){
              this.name = name;
          }
          public String getName(){
              return name;
          }
          protected void setPrice(int price){
              this.price = price;
          }
          protected int getPrice(){
              return price;
          }
          protected abstract void decorateBeverage();
      
      }
      class Tea extends Beverage{
          public Tea(String name){
              super(name);
              setPrice(10);
          }
          public void decorateBeverage(){
              System.out.println("Cost of:"+ name +":"+ price);
              // You can add some more functionality
          }
      }
      class Coffee extends Beverage{
          public Coffee(String name){
              super(name);
              setPrice(15);
          }
          public void decorateBeverage(){
              System.out.println("Cost of:"+ name +":"+ price);
              // You can add some more functionality
          }   
      }
      abstract class BeverageDecorator extends Beverage {
          protected Beverage beverage;
          public BeverageDecorator(Beverage beverage){    
              this.beverage = beverage;   
              setName(beverage.getName()+"+"+getDecoratedName());
              setPrice(beverage.getPrice()+getIncrementPrice());
          }
          public void decorateBeverage(){
              beverage.decorateBeverage();
              System.out.println("Cost of:"+getName()+":"+getPrice());
          }   
          public abstract int getIncrementPrice();
          public abstract String getDecoratedName();
      }
      class SugarDecorator extends BeverageDecorator{
          public SugarDecorator(Beverage beverage){
              super(beverage);
          }
          public void decorateBeverage(){
              super.decorateBeverage();
              decorateSugar();        
          }
          public void decorateSugar(){
              System.out.println("Added Sugar to:"+beverage.getName());
          }
          public int getIncrementPrice(){
              return 5;
          }
          public String getDecoratedName(){
              return "Sugar";
          }
      }
      class LemonDecorator extends BeverageDecorator{
          public LemonDecorator(Beverage beverage){
              super(beverage);
          }
          public void decorateBeverage(){
              super.decorateBeverage();
              decorateLemon();    
          }
          public void decorateLemon(){
              System.out.println("Added Lemon to:"+beverage.getName());       
          }
          public int getIncrementPrice(){
              return 3;
          }
          public String getDecoratedName(){
              return "Lemon";
          }
      }
      
      public class VendingMachineDecorator {  
          public static void main(String args[]){
              Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
              beverage.decorateBeverage();
              beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
              beverage.decorateBeverage();
          }
      }
      

      输出:

      Cost of:Assam Tea:10
      Cost of:Assam Tea+Lemon:13
      Added Lemon to:Assam Tea
      Cost of:Assam Tea+Lemon+Sugar:18
      Added Sugar to:Assam Tea+Lemon
      Cost of:Cappuccino:15
      Cost of:Cappuccino+Lemon:18
      Added Lemon to:Cappuccino
      Cost of:Cappuccino+Lemon+Sugar:23
      Added Sugar to:Cappuccino+Lemon
      

      此示例计算了在饮料中添加多种口味后自动售货机中的饮料成本。

      在上面的例子中:

      茶的成本= 10,柠檬= 3,糖= 5.如果你制作糖+柠檬+茶,则需要18美元。

      咖啡的成本= 15,柠檬= 3,糖= 5.如果你制作糖+柠檬+咖啡,则花费23

      通过对两种饮料(茶和咖啡)使用相同的装饰器,减少了子类的数量。如果没有Decorator模式,您应该为不同的组合使用不同的子类。

      组合将如下:

      SugarLemonTea
      SugarTea
      LemonTea
      
      SugarLemonCapaccuino
      SugarCapaccuino
      LemonCapaccuino
      

      等。

      通过对两种饮料使用相同的装饰器,子类的数量已经减少。由于此模式中使用的组合而不是继承概念,这是可能的。

      相关的SE问题:

      Decorator Pattern for IO

      有用的链接:

      design-patterns-decorator来自dzone

      源代码制作

      decorator

      oodesign文章

答案 4 :(得分:7)

装饰器很简单但非常强大。它是实现关注点分离的关键,也是开放封闭原则的重要工具。举一个订购产品的常见例子:

IOrderGateway
{
    void PlaceOrder(Order order);
{

主要实施:AmazonAffiliateOrderGateway

可能的装饰者可能是:

  • IncrementPerformanceCounterOrderGateway
  • QueueOrderForLaterOnTimeoutOrderGateway
  • EmailOnExceptionOrderGateway
  • InterceptTestOrderAndLogOrderGateway

来自here的更详细示例说明了一个装饰器,它可以保存订单的联系人,这些订单是在完成订单时使用礼品卡创建的:

class OrderWithGiftCardGateway extends OrderGatewayDecorator
{
    ...

    public function createOrder(CreateOrderRequest $order)
    {
        if ($this->containsGiftCard($order))
        {
            $this->addContactToFolder($order);
        }

        return parent::createOrder($order);
    }
}

答案 5 :(得分:3)

Zend Framework使用表单元素的装饰器

更多信息:http://framework.zend.com/manual/en/zend.form.decorators.html

答案 6 :(得分:2)

  1. 动态,透明地为各个对象添加职责。
  2. 可以撤回的责任。
  3. 通过子类扩展是不切实际的。有时可能存在大量独立扩展,并会产生大量子类以支持每种组合。

答案 7 :(得分:0)

装饰器模式由C#语言本身使用。它用于装饰C#的Stream I / O类。装饰的版本为BufferedStream,FileStrem,MemoryStrem,NetworkStream和CryptoStream分类。

这些子类继承自Stream类,并且还包含Stream类的实例。

了解更多here

答案 8 :(得分:0)

对我来说是一个非常真实的例子:

我不得不更新一个在项目中经常使用的类,但是该类在一个库中,并且源代码在网络世界中丢失了。

我可以反编译整个库以创建它的另一个版本,也可以使用装饰器设计模式来完成。这使我能够添加缺少的功能并只需更新配置。

以负责任的方式使用时,这是一个非常有用的模式。

这个特殊的案例启发了我做出this video来解释模式。

答案 9 :(得分:0)

当您想向一个类/对象添加多个功能并且想要灵活地在需要时添加它们时,Decorator会派上用场。您可以扩展基类并添加所需的更改,但是通过这种方式,您想到了很多子类,这些类可能会让您大吃一惊。但是使用装饰器,您可以进行所需的更改,但仍具有简单的理解流程。 您的设计很容易扩展,但实际上逻辑却很接近修改。 最好的示例可能是用Java和C#实现的Stream对象。 例如,您有一个文件流,并且在一个用例中,您想要对其进行加密,然后对其进行压缩,然后对其进行记录,最后对其进行处理。然后在另一个类中,您决定执行其他操作。要对其进行转换,然后对其进行加密,然后获取时间bluh,bluh,bluh。同样,您在其他课程中还有另一个流程。 如果要使用继承,则必须至少创建3个子类,并且如果需要任何其他要求,则必须添加更多的子类,在这种情况下(Stream),您将最终获得数十个子类以进行较小的更改

class EncryptZipLogStream{}
class ConvertEncryptTimeStream{}
class MoreStreamProcess{}
class OtherMoreStreamProcess{}
...

在每个用例中,您都必须记住需要什么类并尝试使用它。 但是,假设您使用了Composition而不是Inheritance,并且为每个Stream流程使用了Decorator类,则可以轻松地组合所需的包装器,并以最少的工作量和最大的简便性拥有所需的流程。

class WhereIWannaUseUseCaseOne {
    EncryptStream(ZipStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

然后您提出另一个用例:

class WhereIWannaUseUseCaseSecond {
    ConvertStream(TimeStream(LogStream(FileStream("file name)))));
    // rest of the code to use the combines streams.
}

依此类推,您可以灵活地以简单的流程和易于理解的逻辑在运行时执行所需的操作。