一、装饰器模式

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

aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvODE1MjIwLzIwMTcwNC84MTUyMjAtMjAxNzA0MTYwMzEyMTc4NjQtMTAxOTA3Mzk3Mi5wbmc.jpg

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

二、案例

现在很多人都喜欢工作之余喝杯咖啡,现在有两种咖啡:Decaf、Espresso,另有两种调味品:Mocha、Whip,每种咖啡的价格不同,所加调料的价格也不同,那么如何计算不同口味的咖啡的价格呢?

最简单的方案当然就是组合出四种咖啡,固定他们的价格,想要哪种组合就直接付多少钱,不考虑咖啡和调味品之间的关系,代码冗余不再赘述

20150724154701506.png

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

改进一下

20150724160235303.png

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

Coffee

public class Coffee {
    private boolean mocha;
    private boolean whip;
    
    public double cost(){
        double price = 0d;
        if(mocha){
            price += 0.5;
        }
        if(whip){
            price += 0.1;
        }
        return price;
    }
    
    public void addMocha(){
        this.mocha = true;
    }
    
    public void addWhip(){
        this.whip = true;
    }
}

Decaf /Espresso

public class Decaf extends Coffee{
    public double cost(){
        double price = super.cost();
        price += 2.0;
        return price;
    }
}
 
public class Espresso extends Coffee {
    public double cost(){
        double price = super.cost();
        price += 2.5;
        return price;
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Coffee coffee = new Decaf();
        coffee.addMocha();
        coffee.addWhip();
        //2.6
        System.out.println(coffee.cost());
    }
}

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

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

2,类本身不够灵活,无法处理顾客希望添加双倍的Mocha的场景

3,添加一种新的咖啡IceCoffee,如果IceCoffee不能加Mocha,由于IceCoffee类继承自Coffee类,IceCoffee类依然从父类继承了addMocha()方法,这就需要在IceCoffee类中重写一个空的addMocha()方法

最后,让我们的设计模式登场,对应结构图

20180520193745408.jpg

  • 抽象组件Component:对应Coffee类
  • 具体组件ConcreteComponent:对应具体的咖啡,如:Decaf,Espresso
  • 装饰者Decorator:对应调味品,如:Mocha,Whip

Coffee

public interface Coffee {
    public double cost();
}

Espresso /Decaf

public class Espresso implements Coffee {
    public double cost(){
        return 2.5;
    }
}
 
public class Decaf implements Coffee {
    public double cost(){
        return 2.0;
    }
}

Decorator

public class Decorator implements Coffee {
    private Coffee coffee;
    
    public Decorator(Coffee coffee){
        this.coffee = coffee;
    }
    
    public double cost(){
        return coffee.cost();
    }
}

Whip /Mocha

public class Whip extends Decorator {
    public Whip(Coffee coffee){
        super(coffee);
    }
    
    public double cost(){
        return super.cost() + 0.1;
    }
}
 
public class Mocha extends Decorator {
    public Mocha(Coffee coffee){
        super(coffee);
    }
    
    public double cost(){
        return super.cost() + 0.5;
    }
}

测试

public class Test {
    public static void main(String[] args) {
        Coffee coffee = new Mocha(new Mocha(new Whip(new Espresso)));
        //3.6(0.5 + 0.5 + 0.1 + 2.5)
        System.out.println(coffee.cost());
    }
}

装饰模式有3个特点:

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

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

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

看到这里是不是想到了Java中很常用的InputStream和OutputStream类?没错,它们都是使用装饰模式设计的

装饰模式的缺点:

1,装饰模式虽然扩展性较高,但是没有设计二简洁,类的数量略多(比设计一少很多),如何取舍可扩展性和简洁性是个问题。如果需求比较明确,并且后期发生变化的概率不大,没必要直接使用装饰模式,设计二更快速高效

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

3,在某些场景下,可能需要按照一定的顺序进行装饰,稍不注意,就会产生异常

装饰模式与建造者模式之间的区别:

装饰模式的构造过程是不稳定的(是否需要装饰,装饰多少层都是可以自由调整的),建造者模式的建造过程是稳定的


版权声明:文章转载请注明来源,如有侵权请联系博主删除!
最后修改:2019 年 12 月 25 日 01 : 50 PM
如果觉得我的文章对你有用,请随意赞赏