第三章:装饰器模式

这章用星巴克咖啡店的例子演示了装饰器模式的使用。

先来看看在星巴克点咖啡的场景:在星巴克你先要点一种饮料,然后你可以加入各种调料(调料也要钱)。

比如:来一份综合咖啡(House Blend),加一份摩卡,再加一份豆浆。那么一共0.89+0.2+0.15=1.24美元。

在比如:来一份综合咖啡,加两份摩卡(我太喜欢摩卡了),再加一份奶泡。

image

下面用装饰模式来实现

调料可以看做一种装饰器,装饰在饮料上面。下面是装饰器模式实现星巴克的UML图。

考虑下面的问题:

  • 令人奇怪的是为什么调料要继承自饮料,装饰器模式必须要求装饰器是被装饰者的子类。你现在肯定对此很不解,下文贴出了main函数代码,仔细阅读main函数你应该能明白为什么。
  • 为什么装饰器要有beverage字段?即,为什么装饰器要记住自己装饰的是哪个对象?

image

先看看客户端生产咖啡的代码。真的是太巧妙了,你可以任意添加各种调料。

public class Client {
    public static void main(String[] args) {
        //综合咖啡 + 摩卡 + 豆浆
        Beverage beverage1 = new HouseBlend();
       beverage1 = new Mocha(beverage1);  //你看,如果调料不是饮料的子类,该语句就出错了
         beverage1 = new Soy(beverage1);
        System.out.println(beverage1.getDescription() + "  $:" + beverage1.cost());
        
        //综合咖啡 + 摩卡 + 摩卡 + 奶泡
        Beverage beverage2 = new HouseBlend();
        beverage2 = new Mocha(beverage2);
        beverage2 = new Mocha(beverage2);
        beverage2 = new Whip(beverage2);
        System.out.println(beverage2.getDescription() + "  $:" + beverage2.cost());
    }
}

输出结果是:

综合咖啡,摩卡  $:1.24
综合咖啡,摩卡  $:1.3900000000000001

实现代码:

//饮料,是抽象类
public abstract class Beverage {
    protected String description = "不知道是什么饮料";
    
    public String getDescription() {
        return description;
    }
    
    public abstract double cost();
}

//调料(装饰器)。要继承自饮料。虽然调料继承自饮料有点奇怪,但装饰器模式必须要这样。
public abstract class CondimentDecorator extends Beverage {
    Beverage beverage;
    
    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;        
    }
}

//综合咖啡,是一种饮料。
public class HouseBlend extends Beverage {
    public HouseBlend() {
        description = "综合咖啡";
    }
    
    @Override
    public double cost() {
        return 0.89;
    }
}

//摩卡,是一种调料。价格0.2美元。
public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
        this.beverage.description += ",摩卡";  //此处代码易错,见下面的错误代码演示
    }
    
    @Override
    public String getDescription() {
        return this.beverage.getDescription();  //此处代码易错,见下面的错误代码演示
    }
    
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }
}

错误代码演示

//这段代码错在description字段
//Whip自己也从基类继承了description字段
//beverage也有一个description字段
//仔细想想到底应该改变哪个字段的值???
public class Whip extends CondimentDecorator {
    Beverage beverage;

    public Whip(Beverage beverage) {
        this.beverage = beverage;
        description += ", 奶泡";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.3;
    }
}
//这段代码改进了上面提到的错误,但是还有一处错误,你仔细想想看吧!
//Soy从基类继承了getDescription()方法
//beverage也有getDescription()方法
//想想我们到底应该调用谁的getDescription()方法???
//那么看来Soy必须override getDescription()方法,并在其中调用beverage的getDescription()方法。
public class Soy extends CondimentDecorator {
    Beverage beverage;

    public Soy(Beverage beverage) {
        this.beverage = beverage;
        this.beverage.description += ", 豆浆";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.1;
    }
}

扩展练习

如果想把星巴克的咖啡带回办公室享受,那么需要支付打包费用。因为杯子要钱,杯子还分豪华杯子和简陋的杯子。我也不知道杯子有什么区别,也许豪华的可以保温吧- -。我扩展了下面这个类图,你可以用Java实现一下。

另外,也许有人觉得调料是饮料的子类并不奇怪(也有人把调料当饮料喝)。但是下面这个类图杯子竟然是饮料的子类!!!不要怀疑,装饰器模式就是这样的~~~

image

原文地址:https://www.cnblogs.com/dongchen/p/5011686.html