模式设计(9)装饰器模式(Decorator)

1、初识装饰器模式

    装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。其结构图如下:

      模式设计(9)装饰器模式(Decorator)

    • Component为统一接口,也是装饰类和被装饰类的基本类型。
    • ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
    • Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。而Decorator本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
    • ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的ConcreteComponent,从而对其进行装饰。

  2、最简单的代码实现装饰器模式

模式设计(9)装饰器模式(Decorator)
//基础接口
public interface Component {
    
    public void biu();
}
//具体实现类
public class ConcretComponent implements Component {

    public void biu() {
        
        System.out.println("biubiubiu");
    }
}
//装饰类
public class Decorator implements Component {

    public Component component;
    
    public Decorator(Component component) {
        
        this.component = component;
    }
    
    public void biu() {
        
        this.component.biu();
    }
}
//具体装饰类
public class ConcreteDecorator extends Decorator {

    public ConcreteDecorator(Component component) {

        super(component);
    }

    public void biu() {
        
        System.out.println("ready?go!");
        this.component.biu();
    }
}
模式设计(9)装饰器模式(Decorator)

    这样一个基本的装饰器体系就出来了,当我们想让Component在打印之前都有一个ready?go!的提示时,就可以使用ConcreteDecorator类了。具体方式如下:

模式设计(9)装饰器模式(Decorator)
  //使用装饰器
  Component component = new ConcreteDecorator(new ConcretComponent());
  component.biu();

  //console:
  ready?go!
  biubiubiu
模式设计(9)装饰器模式(Decorator)

  3、为何使用装饰器模式

    一个设计模式的出现一定有他特殊的价值。仅仅看见上面的结构图你可能会想,为何要兜这么一圈来实现?仅仅是想要多一行输出,我直接继承ConcretComponent,或者直接在另一个Component的实现类中实现不是一样吗?

    首先,装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能。在一个继承的体系中,子类通常是互斥的。比如一辆车,品牌只能要么是奥迪、要么是宝马,不可能同时属于奥迪和宝马,而品牌也是一辆车本身的重要属性特征。但当你想要给汽车喷漆,换坐垫,或者更换音响时,这些功能是互相可能兼容的,并且他们的存在不会影响车的核心属性:那就是他是一辆什么车。这时你就可以定义一个装饰器:喷了漆的车。不管他装饰的车是宝马还是奥迪,他的喷漆效果都可以实现。

    再回到这个例子中,我们看到的仅仅是一个ConcreteComponent类。在复杂的大型项目中,同一级下的兄弟类通常有很多。当你有五个甚至十个ConcreteComponent时,再想要为每个类都加上“ready?go!”的效果,就要写出五个子类了。毫无疑问这是不合理的。装饰器模式在不影响各个ConcreteComponent核心价值的同时,添加了他特有的装饰效果,具备非常好的通用性,这也是他存在的最大价值。


第二个例子:

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

看下下面的例子,总共有两种咖啡:Decaf、Espresso,另有两种调味品:Mocha、Whip(3种设计的主要差别在于抽象方式不同)

设计一:

模式设计(9)装饰器模式(Decorator)

即使添加在多的调味品,咖啡依然是咖啡,在抽象的过程中并没有考虑咖啡和调味品之间的关系

当咖啡和调味品的种类很多时,将会产生大量的类,如果一种咖啡的价格发生变动,需要找到所有相关的类逐一修改

设计二:

模式设计(9)装饰器模式(Decorator)

将调味品作为Coffee类的属性,比起设计一,类的数量大大减少,相应的,程序结构也更加清晰

[java] view plain copy
  1. public class Coffee {  
  2.     private boolean mocha;  
  3.     private boolean whip;  
  4.       
  5.     public double cost(){  
  6.         double price = 0d;  
  7.         if(mocha){  
  8.             price += 0.5;  
  9.         }  
  10.         if(whip){  
  11.             price += 0.1;  
  12.         }  
  13.         return price;  
  14.     }  
  15.       
  16.     public void addMocha(){  
  17.         this.mocha = true;  
  18.     }  
  19.       
  20.     public void addWhip(){  
  21.         this.whip = true;  
  22.     }  
  23. }  
[java] view plain copy
  1. public class Decaf extends Coffee{  
  2.     public double cost(){  
  3.         double price = super.cost();  
  4.         price += 2.0;  
  5.         return price;  
  6.     }  
  7. }  
[java] view plain copy
  1. public class Espresso extends Coffee {  
  2.     public double cost(){  
  3.         double price = super.cost();  
  4.         price += 2.5;  
  5.         return price;  
  6.     }  
  7. }  

测试一下:

[java] view plain copy
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         Coffee coffee = new Decaf();  
  4.         coffee.addMocha();  
  5.         coffee.addWhip();  
  6.         //2.6  
  7.         System.out.println(coffee.cost());  
  8.     }  
  9. }  

考虑到下面几个问题,设计二有明显的不足:

1,如果调味品的种类较多,Coffee类将会变得相当庞大,难以维护

2,无法处理顾客希望添加双倍的Mocha的场景

3,添加一种新的咖啡IceCoffee,从逻辑上来说IceCoffee是不能加Mocha的,但是由于IceCoffee类继承自Coffee类,IceCoffee类依然从父类继承了addMocha()方法,这就需要在IceCoffee类中重写一个空的addMocha()方法,并且当使用IceCoffee类时,不能够面向Coffee类编程,以避免错误的调用父类方法

设计三--装饰器模式:

模式设计(9)装饰器模式(Decorator)

装饰模式分为3个部分:

1,抽象组件 -- 对应Coffee类

2,具体组件 -- 对应具体的咖啡,如:Decaf,Espresso

3,装饰者 -- 对应调味品,如:Mocha,Whip

装饰模式有3个特点:

1,具体组件和装饰者都继承自抽象组件(Decaf、Espresson、Mocha和Whip都继承自Coffee),并且装饰者持有抽象组件的引用

2,可以使用装饰者组合具体组件创造出新的类(Mocha组合Decaf创造出MochaDecaf)

3,过程2可以重复,直到创造出需要的类

使用装饰模式,想要获得一个WhipDoubleMochaEspresso是很容易的:

模式设计(9)装饰器模式(Decorator)
[java] view plain copy
  1. public interface Coffee {  
  2.     public double cost();  
  3. }  
[java] view plain copy
  1. public class Espresso implements Coffee {  
  2.     public double cost(){  
  3.         return 2.5;  
  4.     }  
  5. }  
[java] view plain copy
  1. public class Dressing implements Coffee {  
  2.     private Coffee coffee;  
  3.       
  4.     public Dressing(Coffee coffee){  
  5.         this.coffee = coffee;  
  6.     }  
  7.       
  8.     public double cost(){  
  9.         return coffee.cost();  
  10.     }  
  11. }  
[java] view plain copy
  1. public class Whip extends Dressing {  
  2.     public Whip(Coffee coffee){  
  3.         super(coffee);  
  4.     }  
  5.       
  6.     public double cost(){  
  7.         return super.cost() + 0.1;  
  8.     }  
  9. }  
[java] view plain copy
  1. public class Mocha extends Dressing {  
  2.     public Mocha(Coffee coffee){  
  3.         super(coffee);  
  4.     }  
  5.       
  6.     public double cost(){  
  7.         return super.cost() + 0.5;  
  8.     }  
  9. }  

测试一下:

[java] view plain copy
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         Coffee coffee = new Espresso();  
  4.         coffee = new Mocha(coffee);  
  5.         coffee = new Mocha(coffee);  
  6.         coffee = new Whip(coffee);  
  7.         //3.6(2.5 + 0.5 + 0.5 + 0.1)  
  8.         System.out.println(coffee.cost());  
  9.     }  
  10. }  

当然Decorator类中可以重写父类的方法,也可以扩展自己需要的方法

 

装饰模式的缺点:

1,装饰模式虽然扩展性较高,但是没有设计二简洁,类的数量略多(但肯定比设计一少很多),如何取舍可扩展性和简洁性是个问题,有所选择就要有所牺牲

2,很难搞清楚一个类究竟被装饰了多少层,可能是1层,也可能是100层