zoukankan      html  css  js  c++  java
  • 装饰者模式

    今天来介绍装饰者模式Decorator Design Pattern)。

    假设我们需要给一家火锅店设计一套结账系统,也就是统计顾客消费的总价格。怎样才能设计出一个好的系统呢?

    1,结账系统需求分析

    既然要设计一个结账系统,当然需要知道火锅店都有哪些食品及食品的价格,假如我们从火锅店老板那里得到以下食品清单:

    • 锅底类:
      • 清汤锅底:5 元
      • 麻辣锅底:7 元
      • 其它
    • 配菜类:
      • 青菜:3 元
      • 羊肉:6 元
      • 其它
    • 饮料类:
      • 可乐:2 元
      • 其它

    可以看到,食品共有三大类,分别是:锅底类,配菜类和饮料类,每个大类下边都有很多具体的食品。

    为了设计出一个可维护,可扩展,有弹性的系统,应该怎样设计呢?

    我们可以这样看待食品之间的关系,将锅底类看作主品,所有其它的都为副品,也就是附加在主品之上的食品。

    副品以主品为中心,围绕在主品周围,包裹着主品,一层层的往外扩展

    如下图所表达的一样:

    在这里插入图片描述

    2,装饰者模式

    像这种,需要在原来(主品)的基础上,附加其它的东西(副品),这样的业务场景都可以使用装饰者模式

    装饰者模式的定义为:动态的给一个对象添加其它功能。从扩展性来说,这种方式比继承更有弹性,更加灵活,可作为替代继承的方案

    装饰者模式的优点在于,它能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。也就是不需要通过修改代码,而是通过扩展代码,来完成新的业务需求。

    这就非常符合我们所说的设计原则中的开闭原则对扩展开放,对修改关闭。也就是尽量不要修改原有代码,而是通过扩展代码来完成任务。这样做的好处是可以减少对原有系统的修改,从而减少引入 bug 的风险。

    装饰者模式的类图如下:

    在这里插入图片描述

    ConcreteComponent 为被装饰者,Decorator 是所有装饰者的超类。

    装饰者和被装饰者有着共同的超类型,这一点很重要,因为装饰者必须能够取代被装饰者。这样,装饰者才能在被装饰者的基础上,加上自己的行为,以增强被装饰者的能力。

    一个被装饰者可以被多个装饰者依次包装,这个包装行为是动态的,不限次数的。

    3,实现结账系统

    那么根据装饰者模式的类图,我们可以设计出火锅店结账系统的类图,如下:

    在这里插入图片描述

    火锅的锅底作为被装饰者,配菜和饮料作为装饰者。

    每个类都有两个方法:

    • describe:返回当前火锅的描述。
    • cost:返回当前火锅的价格。

    首先编写 HotPot 类:

    class HotPot {
        protected String desc = "HotPot";
        protected double price = 0;
    
        public String description() {
            return desc;
        }
    
        public double cost() {
            return price;
        }
     
        public void printMenu() {
            System.out.println("菜单:" + description() + " 消费总价:" + cost());
        }
    }
    

    HotPot 类中有两个属性 desc 和 price,还有三个方法 descriptioncost 和 printMenuprintMenu 用于输出菜单和消费总价。

    再编写 SideDish 类:

    class SideDish extends HotPot {
        protected HotPot hotpot;
        
        public double cost() {
            return hotpot.cost() + price;
        };
    
        public String description() {
            return hotpot.description() +" + "+ desc;
        };
    }
    

    SideDish 继承了 HotPot,添加了自己的属性 hotpot,并且重写了两个方法 description 和 cost

    注意SideDish 类对两个方法 description 和 cost 进行了重写,这非常重要,这体现出了装饰者与被装饰者的区别,装饰者能在被装饰者的基础上附加自己的行为,原因就在这里

    编写两个锅底类:

    class SoupPot extends HotPot {
        public SoupPot() {
            desc = "Soup";
            price = 5;
        }
    }
    
    class SpicyPot extends HotPot {
        public SpicyPot() {
            desc = "Spicy";
            price = 7;
        }
    }
    

    这两个类都继承HotPot,并分别在构造方法中设置自己的 desc 和 price

    再编写三个配菜类:

    class VegetablesDish extends SideDish {
        public VegetablesDish(HotPot hotpot) {
            this.hotpot = hotpot;
            desc = "Vegetables";
            price = 3;
        }
    }
    
    class MuttonDish extends SideDish {
        public MuttonDish(HotPot hotpot) {
            this.hotpot = hotpot;
            desc = "Mutton";
            price = 6;
        }
    }
    
    class ColaDish extends SideDish {
        public ColaDish(HotPot hotpot) {
            this.hotpot = hotpot;
            desc = "Cola";
            price = 2;
        }
    }
    

    这三个类都继承 SideDish,并分别在构造方法中设置自己的hotpotdesc 和 price

    4,测试结账系统

    用如下代码来测试:

    // 只有一份清汤锅底
    HotPot hotpot = new SoupPot(); // 被装饰者不需装饰者包装也可以使用
    hotpot.printMenu();
    
    // 清汤锅底 + 蔬菜
    hotpot = new VegetablesDish(hotpot);
    hotpot.printMenu();
    
    // 清汤锅底 + 蔬菜 + 羊肉
    hotpot = new MuttonDish(hotpot);
    hotpot.printMenu();
    
    // 清汤锅底 + 蔬菜 + 羊肉 + 可乐
    hotpot = new ColaDish(hotpot);
    hotpot.printMenu();
    
    // 清汤锅底 + 蔬菜 + 羊肉 + 可乐 + 蔬菜
    hotpot = new VegetablesDish(hotpot);
    hotpot.printMenu();
    

    输出如下:

    菜单:Soup 消费总价:5.0
    菜单:Soup + Vegetables 消费总价:8.0
    菜单:Soup + Vegetables + Mutton 消费总价:14.0
    菜单:Soup + Vegetables + Mutton + Cola 消费总价:16.0
    菜单:Soup + Vegetables + Mutton + Cola + Vegetables 消费总价:19.0
    

    计算总价时,会从最外层的装饰者,朝着被装饰者的方向,依次调用每一层的 cost 方法,直到被装饰者为止。

    然后再朝着最外层装饰者的方向,依次计算出每一层的价格,最后得出的价格就是消费总价。

    计算过程如下图所示:

    在这里插入图片描述

    我将完整的装饰者模式代码放在了这里,供大家参考。

    5,装饰者模式的使用场景

    装饰者模式主要用于,在不修改原有类的前提下,动态的修改原有类的功能。

    Java JDK 中大量使用了装饰者模式,尤其是 Java I/O 框架。

    Java IO 框架的继承关系如下:

    在这里插入图片描述

    可以看到,Java IO 框架包含了非常多的类,这对初学者并不是很友好,很难弄明白每个类的作用是什么,也不容易了解设计者的意图。

    Java IO 主要分为字节流字符流两大类。我们以 InputStream 为例,画出其类图结构,如下:

    在这里插入图片描述

    从该图能够看出,Java IO 与我们上文中的装饰者模式的类图基本一模一样,所以 Java IO 其实就是使用了装饰者模式,明白了这一点,再使用它就非常方便了。

    6,装饰者模式的缺点

    装饰者模式有一个比较明显的缺点,从上文中你也许已经发现了,就是它会引入非常多的小类,这样会让使用者弄不明白类之间的关系。

    当了解了装饰者的原理,也就比较容易使用了。

    7,总结

    从装饰者模式中,能充分的看到开闭原则的使用。利用装饰者模式,可以让我们在不修改原有代码的情况下,扩展原有类的功能。但是也不能过度使用它,因为容易引入非常多的小类。

  • 相关阅读:
    [NoiPlus2016]换教室
    [HNOI2013]游走
    [Noi2002]Savage
    [SDOI2010]古代猪文
    [JSOI2008]最小生成树计数
    [SCOI2010] 连续攻击游戏
    文艺平衡树
    指针FHQTreap
    HAOI2007 上升序列
    HNOI2008 玩具装箱
  • 原文地址:https://www.cnblogs.com/ma13461749958/p/14247399.html
Copyright © 2011-2022 走看看