• 第三章:装饰器模式


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

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

    比如:来一份综合咖啡(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

  • 相关阅读:
    ckeditor(在线文本编辑器)使用教程
    一张图轻松搞懂javascript event对象的clientX,offsetX,screenX,pageX区别
    正则表达式
    关于padding与margin的区别
    伪类link,hover,active,visited,focus的区别
    运动框架
    scroll、offset和client的区别
    如何给Sublime安装插件
    获取行间样式与在js中设置样式
    寻找下一个同级元素节点
  • 原文地址:https://www.cnblogs.com/dongchen/p/5011686.html
Copyright © 2020-2023  润新知