公号:码农充电站pro
主页:https://codeshellme.github.iohtml
今天来介绍装饰者模式(Decorator Design Pattern)。java
假设咱们须要给一家火锅店设计一套结帐系统,也就是统计顾客消费的总价格。怎样才能设计出一个好的系统呢?git
既然要设计一个结帐系统,固然须要知道火锅店都有哪些食品及食品的价格,假如咱们从火锅店老板那里获得如下食品清单:github
能够看到,食品共有三大类,分别是:锅底类,配菜类和饮料类,每一个大类下边都有不少具体的食品。算法
为了设计出一个可维护,可扩展,有弹性的系统,应该怎样设计呢?shell
咱们能够这样看待食品之间的关系,将锅底类看做主品,全部其它的都为副品,也就是附加在主品之上的食品。设计模式
副品以主品为中心,围绕在主品周围,包裹着主品,一层层的往外扩展。框架
以下图所表达的同样:ide
像这种,须要在原来(主品)的基础上,附加其它的东西(副品),这样的业务场景均可以使用装饰者模式。测试
装饰者模式的定义为:动态的给一个对象添加其它功能。从扩展性来讲,这种方式比继承更有弹性,更加灵活,可做为替代继承的方案。
装饰者模式的优势在于,它可以更灵活的,动态的给对象添加其它功能,而不须要修改任何现有的底层代码。也就是不须要经过修改代码,而是经过扩展代码,来完成新的业务需求。
这就很是符合咱们所说的设计原则中的开闭原则:对扩展开放,对修改关闭。也就是尽可能不要修改原有代码,而是经过扩展代码来完成任务。这样作的好处是能够减小对原有系统的修改,从而减小引入 bug 的风险。
装饰者模式的类图以下:
ConcreteComponent 为被装饰者,Decorator 是全部装饰者的超类。
装饰者和被装饰者有着共同的超类型,这一点很重要,由于装饰者必须可以取代被装饰者。这样,装饰者才能在被装饰者的基础上,加上本身的行为,以加强被装饰者的能力。
一个被装饰者能够被多个装饰者依次包装,这个包装行为是动态的,不限次数的。
那么根据装饰者模式的类图,咱们能够设计出火锅店结帐系统的类图,以下:
火锅的锅底做为被装饰者,配菜和饮料做为装饰者。
每一个类都有两个方法:
首先编写 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
,还有三个方法 description
,cost
和 printMenu
。printMenu
用于输出菜单和消费总价。
再编写 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
,并分别在构造方法中设置本身的hotpot
,desc
和 price
。
用以下代码来测试:
// 只有一份清汤锅底 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
方法,直到被装饰者为止。
而后再朝着最外层装饰者的方向,依次计算出每一层的价格,最后得出的价格就是消费总价。
计算过程以下图所示:
我将完整的装饰者模式代码放在了这里,供你们参考。
装饰者模式主要用于,在不修改原有类的前提下,动态的修改原有类的功能。
Java JDK 中大量使用了装饰者模式,尤为是 Java I/O 框架。
Java IO 框架的继承关系以下:
能够看到,Java IO 框架包含了很是多的类,这对初学者并非很友好,很难弄明白每一个类的做用是什么,也不容易了解设计者的意图。
Java IO 主要分为字节流和字符流两大类。咱们以 InputStream 为例,画出其类图结构,以下:
从该图可以看出,Java IO 与咱们上文中的装饰者模式的类图基本如出一辙,因此 Java IO 其实就是使用了装饰者模式,明白了这一点,再使用它就很是方便了。
装饰者模式有一个比较明显的缺点,从上文中你也许已经发现了,就是它会引入很是多的小类,这样会让使用者弄不明白类之间的关系。
当了解了装饰者的原理,也就比较容易使用了。
从装饰者模式中,能充分的看到开闭原则的使用。利用装饰者模式,可让咱们在不修改原有代码的状况下,扩展原有类的功能。可是也不能过分使用它,由于容易引入很是多的小类。
(本节完。)
推荐阅读:
欢迎关注做者公众号,获取更多技术干货。