[设计模式笔记] No.3 装饰者模式(Decorator)

装饰者模式

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

需求

星巴兹咖啡店准备更新订单系统,以合乎他们的饮料供求。提供一个Beverage(饮料)类,是一个抽象类,店内所提供的饮料都必须继承自此类。此外,购买咖啡时,也可以要求在其中加入各种调料,例如:豆浆(soy)、摩卡(Mocha)、覆盖奶泡。星巴兹会根据所加入的调料收取不同的费用。所以订单系统必须考虑到这些调料
Beverage类图如下:

[设计模式笔记] No.3 装饰者模式(Decorator)_第1张图片
decorator_01.jpg

设计原则五

类应该对扩展开放,对修改关闭。

  • 我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为。如能实现这样的目标,有什么好处?这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。
  • 开闭原则,虽然似乎有点矛盾,但是的确有一些技术可以允许不直接修改代码的情况下直接对其进行扩展。
  • 在选择需要被扩展的代码部分时要小心。每个地方都采用开放-闭合原则,是一种浪费,也没有必要,还会导致代码变得复杂且难以理解。
认识 装饰者模式

我们以饮料为主体,然后在运行时,以调料来“装饰”(decorate)饮料。比方说,如果顾客想要摩卡和奶泡深培咖啡,那么要做的是:

  1. 哪一个深培咖啡(DarkRoast)对象
  2. 以摩卡(Mocha)对象装饰它
  3. 以奶泡(Whip)对象装饰它
  4. 调用cost()方法,并依赖委托(delegate)将调料的价格加上去
[设计模式笔记] No.3 装饰者模式(Decorator)_第2张图片
decorator_02.jpg
目前已知
  • 装饰者和被装饰者有相同的超类型
  • 可以用一个或者多个装饰者包装一个对象
  • 既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的对象)的场合,可以用装饰过的对象代替它
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,以达到特定的目的。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地,不限量地用你喜欢的装饰者来装饰对象。
[设计模式笔记] No.3 装饰者模式(Decorator)_第3张图片
decorator_04.jpg

应用

[设计模式笔记] No.3 装饰者模式(Decorator)_第4张图片
decorator_05.jpg
代码:
饮料超类,星巴兹提供的抽象类
/**
 * 描述:饮料超类,星巴兹提供的抽象类
 * 程序狗: wengxingxia
 * 创建日期: 2018/7/3 8:35
 */
public abstract class Beverage {
    protected String mDescription = "Unknow Beverage";

    public String getDescription() {
        return mDescription;
    }

    public abstract double cost();
}

深培咖啡 DarkRoast
/**
 * 描述:具体的饮料——深培咖啡
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:50
 */
public class DarkRoast extends Beverage {

    public DarkRoast() {
        this.mDescription = "DarkRoast Coffee";
    }

    @Override
    public double cost() {
        return 0.99;
    }
}

浓缩咖啡 Espresso
/**
 * 描述:具体的饮料——浓缩咖啡
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:50
 */
public class Espresso extends Beverage {

    public Espresso() {
        this.mDescription = "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

综合咖啡 HouseBlend
/**
 * 描述:具体的饮料——综合咖啡
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:50
 */
public class HouseBlend extends Beverage {

    public HouseBlend() {
        this.mDescription = "HouseBlend Coffee";
    }

    @Override
    public double cost() {
        return 0.89;
    }
}
CondimentDecorator 调料抽象类,也就是装饰者类
/**
 * 描述:调料抽象类,也就是装饰者类
 *
 * 首先,必须让 CondimentDecorator 能取代 Beverage,
 * 所以CondimentDecorator扩展自 Beverage
 *
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:45
 */
public abstract class CondimentDecorator extends Beverage{

    public abstract String getDescription();
}
具体的调料(装饰者)——摩卡
/**
 * 描述:具体的调料(装饰者)——摩卡
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:56
 */
public class Mocha extends CondimentDecorator {
    private Beverage mBeverage;

    public Mocha(Beverage beverage) {
        mBeverage = beverage;
    }

    /**
     * 不只是描述饮料,而是完整地连调料都描述出来,
     * 所以利用委托的做法,得到一个叙述,然后在其后加上附加的叙述
     *
     * @return
     */
    @Override
    public String getDescription() {
        return mBeverage.getDescription()+", Mocha";
    }

    /**
     * 要计算带Mocha饮料的价钱,首先把调用委托给“装饰对象”,
     * 以计算价钱,然后加上Mocha的价钱,得到最后的结果
     *
     * @return
     */
    @Override
    public double cost() {
        return 0.20 + mBeverage.cost();
    }
}

具体的调料(装饰者)——豆浆
/**
 * 描述:具体的调料(装饰者)——豆浆
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:56
 */
public class Soy extends CondimentDecorator {
    private Beverage mBeverage;

    public Soy(Beverage beverage) {
        mBeverage = beverage;
    }

    /**
     * 不只是描述饮料,而是完整地连调料都描述出来,
     * 所以利用委托的做法,得到一个叙述,然后在其后加上附加的叙述
     *
     * @return
     */
    @Override
    public String getDescription() {
        return mBeverage.getDescription()+", Soy";
    }

    /**
     * 要计算带Soy饮料的价钱,首先把调用委托给“装饰对象”,
     * 以计算价钱,然后加上Soy的价钱,得到最后的结果
     *
     * @return
     */
    @Override
    public double cost() {
        return 0.15 + mBeverage.cost();
    }
}

具体的调料(装饰者)——奶泡
/**
 * 描述:具体的调料(装饰者)——奶泡
 * 程序狗: Xander
 * 创建日期: 2018/07/03 08:56
 */
public class Whip extends CondimentDecorator {
    private Beverage mBeverage;

    public Whip(Beverage beverage) {
        mBeverage = beverage;
    }

    /**
     * 不只是描述饮料,而是完整地连调料都描述出来,
     * 所以利用委托的做法,得到一个叙述,然后在其后加上附加的叙述
     *
     * @return
     */
    @Override
    public String getDescription() {
        return mBeverage.getDescription()+", Whip";
    }

    /**
     * 要计算带Whip饮料的价钱,首先把调用委托给“装饰对象”,
     * 以计算价钱,然后加上Whip的价钱,得到最后的结果
     *
     * @return
     */
    @Override
    public double cost() {
        return 0.10 + mBeverage.cost();
    }
}

测试
/**
 * 描述:星巴兹咖啡店——测试类
 * 程序狗: Xander
 * 创建日期: 2018/07/03 09:05
 */
public class StarbuzzCoffee {

    public static void main(String args[]) {
//        订一杯 Espresso,不需要调料,打印出描述和价钱
        Beverage beverage = new Espresso();
        System.out.println(beverage.getDescription() + " $" + beverage.cost());

//        两倍Mocha加奶泡的深培咖啡
        Beverage beverage2 = new DarkRoast();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

        Beverage beverage3 = new HouseBlend();
        beverage3 = new Soy(beverage3);
        beverage3 = new Mocha(beverage3);
        beverage3 = new Whip(beverage3);
        System.out.println(beverage3.getDescription() + " $" + beverage3.cost());

    }
}

结果
Espresso $1.99
DarkRoast Coffee, Mocha, Mocha, Whip $1.49
HouseBlend Coffee, Soy, Mocha, Whip $1.34
要点
  • 继承属于扩展形式之一,但不见得是达到弹性设计的最佳方案。
  • 在我们的设计中,应该允许行为可以被扩展,但无须修改现有的代码
  • 组合和委托可用于在运行时动态地加上新的行为
  • 除了继承,装饰者模式也可以让我们扩展行为
  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
  • 装饰者类反映出被装饰组件的类型(事实上,他们具有相同的类型,都经过接口或者继承实现)
  • 装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的
  • 你可以用无数个装饰者包装一个组件
  • 装饰者一般对组件的客户时透明的,除非客户程序依赖于组件的具体类型
  • 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

感谢你的耐心阅读,装饰者模式基本知识和应用就介绍到这里了。

最后回顾一下

我们的设计工具箱中的工具
1、OO基础

① 抽象
② 封装
③ 多态
④ 继承

2、OO原则

① 封装变化
② 多用组合,少用继承
③ 针对接口编程,不针对实现编程
④ 为交互对象之间的松耦合设计而努力
⑤ 对扩展开放,对修改关闭

3、OO模式

① 策略模式——定义算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
② 观察者模式——在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会受到通知并自动更新。
③ 装饰者模式——动态地将责任附加到对象上,若要扩展功能,装饰者提供有别于继承的另一种选择。

感谢阅读!
No.2 观察者模式(Observer)
No.1 策略模式(Strategy)
前言 为何要使用设计模式

Demo代码

你可能感兴趣的:([设计模式笔记] No.3 装饰者模式(Decorator))