装饰模式

1.定义

23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

2.问题和解决方案

咖啡店在街头随处可见。我们以咖啡店的饮品订单系统为例。假设我们要设计一个饮品的订单系统。
设计了一个这样的类图:

Beverage是一个 抽象类,所有咖啡店的饮品都必须继承这个类,description是饮品的描述信息,cost()是计算此种饮品的价格。

我们会遇到这样的问题,在购买饮品的时候,我们可以要求在其中加入不同的调料配品,比如,摩卡(Mocha),加奶泡等。除了原本饮料需要的价格的外,咖啡店会根据所加入调料的再收取不同的费用。

如果按照之前的设计方式,那么会出现如下的情况:

显然这似乎已经是类爆炸了

而且我们永远无法预测,顾客会选取怎样的调料的搭配,每当出现一个新的调料搭配时,我们就需要增加一个新的类。

扫描二维码关注公众号,回复: 176034 查看本文章

更加糟糕的是,当原料配料的价格上涨后或者下降后,那么所有涉及到这种配料的类都得重新改过。这简直是个噩梦!很显然这很不符合我们设计模式的原则。

这里就需要用到我们的装饰者模式

以装饰者的思想构建饮料可以理解为:将饮料作为一个主体,调料作为装饰,主体和装饰是分离的,装饰可以以任何顺序和数量动态添加到主体上。也体现出组合的效果,不用在现有的代码上做任何修改,只需要添加新功能就可以(不用改变主饮料,按需求意愿添加调料),组合效果如图

装饰者可以一层层的把主体包裹起来,那么装饰者(两种调料Mocha和Soy)和主体(一种叫HouseBlend的咖啡)的类型应该保持一致。

让我们转换思路,我们以饮品beverage为主体,在运行时以顾客选择的调料来装饰beverage。比如,如果顾客想要摩卡和奶泡的拿铁咖啡,我们要做的应该是这样的:

  • 取一个拿铁咖啡的对象

  • 用摩卡对象装饰它

  • 用奶泡对象装饰它

  • 调用cost方法计算价钱,并依赖委托将配料摩卡和奶泡加上去。

会先计算whip的cost然后调用mocha的cost,然后调用拿铁的cost,这样就计算出了总价格。
这样就是实现的装饰者模式解决这个问题的思路。
下面我们看一下装饰者模式的定义,以及代码实现的基本思路

3.结构
装饰者模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

这个类图就是装饰者模式的实现方式。更详细的是如下这个版本的类图。

抽象组件(Component)角色:组件对象的接口,可以给这些对象动态地添加职责。

具体组件(ConcreteComponent)角色:具体的组件对象,实现组件对象接口,通过就是被装饰器装饰的原始对象,也就是可以给这个对象添加额外职责。

装饰器(Decorator)角色:所有装饰器的抽象父类,需要定义一个与组件接口相一致的接口,并持有一个Component对象,本质上就是持有一个被装饰的对象。

具体装饰器(ConcreteDecorator)角色:实际的装饰器对象,实现具体的要向被告装饰对象添加的额外功

抽象组件角色Component

public abstract class Component{
       public abstract void Operation();
}

具体组件角色ConcreteComponent

public class ConcreteComponent extends Component{
      public void Operation(){
          System.out.println("ConcreteComponent is doing!");
      }
}

装饰器角色 

public abstract class Decorator extends Component{
      protected Component component;
   
      public Decorator(Component component){
          this.component=component;
      }
   
      public void setComponent(Component component) {
          this.component = component;
      }
   
      public void Operation(){
          component.Operation();
      }
}

具体装饰器角色ConcreteComponent

public class ConcreteDecoratorA extends Decorator{
      public ConcreteDecoratorA(Component component){
          super(component);
      }
      private String addState;
      public String getAddState() {
          return addState;
      }
      public void setAddState(String addState) {
          this.addState = addState;
      }
   
      public void Operation(){
          //调用相关状态信息
          getAddState();
          component.Operation();
      }
}
public class ConcreteDecoratorB extends Decorator{
      public ConcreteDecoratorB(Component component){
          super(component);
      }
  
      public void Addhavior(){
          System.out.println("add a Behavior!");
      }
  
      public void Operation(){
          component.Operation();
          Addhavior();
      }
}

Client

public class Client{
      public static void main(String[] args){
          Component component=new ConcreteComponent();
   
          Decorator decorator1=new ConcreteDecoratorA(component);
          decorator1.Operation();
   
          //直接给装饰对象进行装饰,这样decorator2就同时具有两个额外功能呢
          Decorator decorator2=new ConcreteDecoratorB(decorator1);
          decorator2.Operation();    
      }
}

下面我们就根据这个类图来解决我们之前在实现咖啡店饮料系统上遇到的问题。

  • beverage相当于抽象的component类,具体的component和decorator都需要继承实现这个抽象类。
  • 四个具体的饮料的类,相当于concrete component!每一个类代表了一个饮料类型。
  • condimentDecorator是抽象的decorator类,它是所有调料类的抽象,它保存了beverage的一个引用。
  • 调料装饰者类继承自condimentDecorator,是各种具体调料的实现,他们都实现了cost方法。

上面有一个非常关键的地方,就是我们注意到装饰者和被装饰者必须是一样的类型,也就是拥有共同的超类。这样做是因为我们要装饰者必须能取代被装饰者。
这样我们就可以利用对象的组合,将调料和饮料的行为组合起来。这符合我们之前提到的设计原则多用组合,少用继承
实现装饰者模式

  • 首先实现beverage和condiment两个抽象类
public abstract class Beverage {
    protected String description = "Unknow Beverage";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}
public abstract class CondimentDecorator extends Beverage {
    public abstract String getDescription();
}
  • 然后我们实现具体的饮料类
public class Coco extends Beverage {
    public Coco(){
        description = "Coco";
    }
    
    public double cost(){
        return 0.89;
    }
}
public class Espresso extends Beverage {
    public Espresso() {
        description = "Espresso";
    }
    
    public double cost() {
        return 1.99;
    }
}
  • 我们再实现具体的装饰者类,也就是调料类
public class Mocha extends CondimentDecorator {
    
    Beverage beverage;
    
    public Mocha(Beverage beverage){
        this.beverage = beverage;
    }
    
    
    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .20 + beverage.cost();
    }

    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Mocha";
    }

}
public class Soy extends CondimentDecorator {
    
    Beverage beverage;
    
    public Soy(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + ", Soy";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .15 + beverage.cost();
    }

}
public class Whip extends CondimentDecorator {
    
    Beverage beverage;
    
    public Whip(Beverage beverage){
        this.beverage = beverage;
    }
    
    @Override
    public String getDescription() {
        // TODO Auto-generated method stub
        return beverage.getDescription() + " , whip";
    }

    @Override
    public double cost() {
        // TODO Auto-generated method stub
        return .10 + beverage.cost();
    }

}
  • 最后编写一个测试类,来测试我们装饰者模式的效果如何
public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Beverage beverage = new Espresso();
        System.out.println( beverage.getDescription() + "$" + beverage.cost());
        
        Beverage beverage2 = new Coco();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println( beverage2.getDescription() + "$" + beverage2.cost());
        
        Beverage beverage3 = new Espresso();
        beverage3 = new Whip(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Soy(beverage3);
        System.out.println( beverage3.getDescription() + "$" + beverage3.cost());
    }

}

java 中的IO流就用到了装饰模式

 public static void main(String[] args) throws IOException{
      DataInputStream din=null;
      try {
          din=new DataInputStream(
                  new BufferedInputStream(
                          new FileInputStream("Test.txt")));
   
          byte bs[]=new byte[din.available()];
          din.read(bs);
          String content=new String(bs);
          System.out.println("文件内容为: "+content);
      } finally{
          din.close();
      }
 }

从上述代码中,我们可以看到,最底层的FileInputStream外层被两个装饰器装饰着,一个是DataInputStream,一个是BufferedInputStream。FileInputStream对象相当于最原始的被装饰组件对象,而BufferedInputStream对象和FileInputStream对象则相当装饰器,示例代码其实就是装饰器的组装过程。大家可能对java中的I/O结构体系并不清楚,可以明确的是,既然I/O流可以通过装饰器模式来组装,那就说明装饰器与具体的组件类要实现相同的接口,下面的类结构图便是java中I/O类图关系,通过它,大家应该就很清楚呢,与装饰模式结构图基本一致,这里我们省去了各个类中接口方法。

从上图,我们可以发现,I/O类结构与装饰模式结构几乎是一样的:

  1. InputStream就相当于装饰模式中的Component
  2. FileInputStream、ObjectInputStream、StringBufferInputStream都实现了InputStream接口,所以它们相当于装饰模式的具体组件类(ConcreteComponent)。
  3. FilterInputStream不仅实现了InputStream接口,还持有InputStream接口对象引用,其实就是装饰模式的Decorator角色,而继承于FilterInputStream的DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream就是具体的装饰器对象。
     

3.实现要点

保持接口的一致性

装饰对象的接口必须与它所装饰品的组件接口保持一致,故所有的具体装饰器都是应该实现同一个公共父类,即Decorator类。而Decorator又继承于抽象组件类Component,因此所有的装饰对象也完全属于组件类型范畴。这样做的好处是Decorator对Component是透明的,Component无须知道Decorator的存在,Decorator是从外部来扩展Component功能。

可省略抽象Decorator类

当我们仅需要给组件添加一个职责或功能时,完全没必要定义抽象Decorator类。这时,可以直接把Decorator向Component转发请求的职责合并到具体装饰器对象(ConcreteDecorator)中。

保持Component类的简单性

为了保证接口的一致性,组件和装饰必须有一个公共的Component类。因而,保持这个类的简单性是很重要的,它应该集中定义接口而不是存储数据,对数据表示的定义延迟到子类,否则Component会变得复杂和庞大,也难以使用。关键是赐予Component太多的功能,对于子类来说未必需要,只会造成臃肿的糟糕设计而已。

改变对象外壳和改变对象内核

Component可以说是组件的内核,而其具体实现由ConcreteComponent来完成,Decorator是组件的外壳,用于改变组件的外在表示和行为。装饰模式主要用于完成对组件外壳的改变,而内核的改变通过是一个改变组件内核的很好模式。不过当Component类原本就比较庞大、复杂时,使用装饰模式代价较高,此时策略模式相对会合适一些,我们可以将组件的一些行为转发给一个独立的策略对象,只需要替换相应的策略对象,就可以改变或者扩充组件的功能。

4.优缺点

优点:

1. Decorator模式与继承关系的目的都是要扩展对象的功能,但是Decorator可以提供比继承更多的灵活性。

2. 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点:

1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

2. 装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂。

3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。当然也可以改变Component接口,增加新的公开的行为,实现“半透明”的装饰者模式。在实际项目中要做出最佳选择。

5.总结

在理解装饰者模式最重要的就是理解组合和委托的两种思想。

装饰模式的本质是:动态组合。动态是手段,组合才是目的。这里的组合有两层意义,一个是动态功能的组合,也就是动态进行装饰器的组合;另一个是指对象组合,通过对象组合来为被装饰对象透明地增加功能。此外装饰模式不仅可以增加功能,亦可以控制功能的访问,完全实现新的功能,同时也可以控制装饰的功能是在装饰功能之前还是之后立即来运行等。总之,装饰模式是通过把复杂功能简单化,分散化,然后在运行期间,根据需要动态组合相应的装饰器,获取相应的职责,这也是为什么需要将装饰器功能尽量细粒度化的原因,有利于复用。

参考:

http://www.jianshu.com/p/bc19e282abf4

https://www.cnblogs.com/JackyTecblog/archive/2012/10/09/2716662.html

猜你喜欢

转载自my.oschina.net/u/3701483/blog/1587801