首页 > 技术文章 > 装饰器的理解和使用

milicool 2019-07-15 15:12 原文

一、是什么?作用

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

二、示例

1. 需求

有一杯焦糖奶茶要制作, 可以添加不同的配料,每种配料的价格不同,最后计算奶茶的价格

2. 原来的做法

1. 原来的做法,第一种最开始想到的, 首先建奶茶基类,然后新建焦糖奶茶类添加该配料的属性, 在价格方法判断是否有改配料,有的话则加上配料的钱。

2. 改进的第二种方法是,在建立一个奶茶的子类,添加一个List属性,用这个属性来存放不同的配料,在计算价格的方法里循环List循环加上配料的价格

总结:

  • 第一种方法问题很大,如果以后需求变化回去修改子类的代码, 破坏了开闭原则, 如果搞个双份椰果,写起来很别扭
  • 第二种方法, 面对配料类没问题, 如果需求在加个按大小杯加钱, 就需要修改子类的代码了,破坏了开闭原则

3. 用了模式的做法

 1. 奶茶基类和子类

/**
 * 奶茶基类
 */
public interface MilkTea {

    /**
     * 价格
     */
    float cost();

    /**
     * 描述
     */
    String desc();
}

// ===================================

/**
 * 焦糖奶茶类
 */
public class CaramelMilkTea implements MilkTea {
    @Override
    public float cost() {
        return 10f;
    }

    @Override
    public String desc() {
        return "焦糖奶茶!";
    }
}

2. 配料基类和子类

 1 /**
 2  * 配料基类
 3  * 配料有很多种, 所以我们抽象出一个基类
 4  */
 5 public abstract class CondimentDecorator implements MilkTea {
 6 
 7     // 将顶层接口以构造参数的方式传递进来
 8     private MilkTea milkTea;
 9 
10     public CondimentDecorator(MilkTea milkTea) {
11         this.milkTea = milkTea;
12     }
13 
14     @Override
15     public float cost() {
16         return milkTea.cost();
17     }
18 
19     @Override
20     public String desc() {
21         return milkTea.desc();
22     }
23 }
24 
25 // =================
26 
27 /**
28  * 配料子类 - 布丁
29  */
30 public class Pudding extends CondimentDecorator {
31 
32     public Pudding(MilkTea milkTea) {
33         super(milkTea);
34     }
35 
36     /**
37      * 重写描述, 告诉顾客加了布丁
38      */
39     @Override
40     public String desc() {
41         return "布丁, " + super.desc();
42     }
43 
44     /**
45      * 重写价格方法, 在原来的价格上添加布丁的钱
46      */
47     @Override
48     public float cost() {
49         return super.cost() + 0.5f;
50     }
51 }

3. 现在可以向顾客提供奶茶了, 测试类

/**
 * 测试主类
 */
public class Main {

    public static void main(String[] args) {
        // 买一杯焦糖奶茶什么都不加
        MilkTea milkTea = new CaramelMilkTea();
        System.out.println("描述: " + milkTea.desc() + ", 价格: " + milkTea.cost());

        // 用布丁装饰奶茶
        MilkTea milkTea2 = new CaramelMilkTea();
        milkTea2 = new Pudding(milkTea2);
        System.out.println("描述: " + milkTea2.desc() + ", 价格: " + milkTea2.cost());

        // 我要一个双份布丁的奶茶, 再用一层布丁装饰奶茶
        MilkTea milkTea3 = new CaramelMilkTea();
        milkTea3 = new Pudding(new Pudding(milkTea3));
        System.out.println("描述: " + milkTea3.desc() + ", 价格: " + milkTea3.cost());

        // todo 如果以后再添加一个需求 每种奶茶有大杯, 中杯, 小杯需要加不同的钱, 只要新建一组杯子基类/ZI类, 重写描述和价格方法就可以了
    }
}

控制台显示

  描述: 焦糖奶茶!, 价格: 10.0
  描述: 布丁, 焦糖奶茶!, 价格: 10.5
  描述: 布丁, 布丁, 焦糖奶茶!, 价格: 11.0

代码小结:

  • 装饰着和杯装饰对象有相同的超类
  • 可以用一个或者多个装饰者包装一个对象
  • 装饰者可以在被装饰者的行为之前或者之后,加上自己的行为,以达到特定的目的

4. 找一个贴近实际的

1. 在java.io类中一系列的InputStream类

三、总结

1. 首先就是开闭原则,对扩展开放,对修改关闭

2. 组合和委托可用于在运行时动态的加上新的行为

推荐阅读