设计模式之装饰器模式 Decorator 包装模式 wrapper 优点 缺点 使用场景 以及简化方法
程序员文章站
2022-04-24 10:11:40
装饰器模式简化继承关系的一种良好的替代方案,可以解决继承的很多问题,所以在动态增加功能时可以考虑,本文对装饰器模式进行了详尽的介绍,主要包括 Decorator 包装模式 wrapper 装饰器模式概念示例UML图 装饰器模式 优点 缺点 使用场景 以及简化方法 ......
引子
现实世界的装饰器模式
大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介
"老板,来一个手抓饼, 加个培根, 加个鸡蛋,多少钱?" |
这句话会不会很耳熟,或者自己可能都说过呢?
我们看看这句话到底表达了哪些含义呢?
你应该可以看得到这两个基本角色
1.手抓饼 核心角色 |
2.配菜(鸡蛋/培根/香肠...) 装饰器角色 |
你既然想要吃手抓饼,自然你是奔着手抓饼去的,对吧
所以,你肯定会要一个手抓饼,至少是原味的
然后可能根据你的口味或者喜好添加更多的配菜
这个行为很自然,也很正常.
如果是在代码的世界里面,你怎么描述: 顾客 购买 手抓饼 这一行为呢?
顾客customer 顾客有方法buy 然后有一个手抓饼handpancake,看起来是这样子的
那么问题来了
如何表示 加了鸡蛋的手抓饼,或者加了鸡蛋和培根的手抓饼呢?
一种很可能方式是把他们都当成手抓饼的不同种类,也就是使用继承或者说实现类的形式
那么我们有多少种手抓饼呢?
原味手抓饼/加鸡蛋手抓饼/加鸡蛋加培根手抓饼/加鸡蛋加烤肠手抓饼/加鸡蛋加培根加烤肠手抓饼手抓饼/.......
很显然,这就是数学中的组合,最终的个数跟我们到底有多少种配菜有关系
|
如果按照这种思维方式,我们将会有无数个手抓饼类,而且如果以后多了一种配菜,类的个数将会呈现爆炸式的增长
这是你想要的结果么?
在现实世界里面,你会很自然的说 "老板,来一个手抓饼, 加个培根, 加个鸡蛋,多少钱?""
|
那么为什么在程序世界里面,你却很可能说"老板,给我来一个加了鸡蛋加了培根的那种手抓饼" 呢? |
手抓饼代码示例
手抓饼接口和具体的一家店铺提供的手抓饼
package decorator; /** * created by noteless on 2018/9/6. * description:手抓饼接口 描述抽象的手抓饼 */ public interface handpancake { /** * 提供手抓饼 */ string offerhandpancake(); /**计算手抓饼的价格 * @return */ integer calccost(); } package decorator; /** * created by noteless on 2018/9/6. * description: noteless 家的手抓饼 */ public class notelesshandpancake implements handpancake { /** * 提供noteless 家的手抓饼一份 */ @override public string offerhandpancake() { return " noteless 家的手抓饼"; } /**计算 noteless 家 一份手抓饼的价格 * @return */ @override public integer calccost() { return 3; } }
配菜抽象类(装饰器)
package decorator; /** * created by noteless on 2018/9/6. * description:装饰器类实现了手抓饼接口,具有了手抓饼的类型 */ public abstract class decorator implements handpancake{ private handpancake handpancake; decorator(handpancake handpancake){ this.handpancake = handpancake; } /**提供手抓饼 * @return */ @override public string offerhandpancake() { return handpancake.offerhandpancake(); } /**提供手抓饼的价格 * @return */ @override public integer calccost() { return handpancake.calccost(); } }
具体的配菜(具体的装饰)
package decorator; /** * created by noteless on 2018/9/6. * description:培根 */ public class bacon extends decorator { bacon(handpancake handpancake){ super(handpancake); } @override public string offerhandpancake() { return super.offerhandpancake()+" 加培根"; } @override public integer calccost() { return super.calccost()+4; } } package decorator; /** * created by noteless on 2018/9/6. * description:鸡蛋 */ public class egg extends decorator { egg(handpancake handpancake){ super(handpancake); } @override public string offerhandpancake() { return super.offerhandpancake()+"加鸡蛋"; } @override public integer calccost() { return super.calccost()+2; } } package decorator; /** * created by noteless on 2018/9/6. * description:烤肠 */ public class sausage extends decorator { sausage(handpancake handpancake){ super(handpancake); } @override public string offerhandpancake() { return super.offerhandpancake()+" 加香肠"; } @override public integer calccost() { return super.calccost()+3; } } package decorator; /** * created by noteless on 2018/9/6. * description:青菜 */ public class vegetable extends decorator { vegetable(handpancake handpancake){ super(handpancake); } @override public string offerhandpancake() { return super.offerhandpancake()+" 加青菜"; } @override public integer calccost() { return super.calccost()+1; } }
顾客
package decorator; /** * created by noteless on 2018/9/6. * description:顾客具有名字,然后购买手抓饼 */ public class customer { private string name; customer(string name){ this.name = name; } public void buy(handpancake handpancake){ system.out.println(name+"购买了 : "+handpancake.offerhandpancake()+ " 一份, 花了 : "+handpancake.calccost()+"块钱~"); system.out.println(); } }
测试类
package decorator; /** * created by noteless on 2018/9/6. * description: * 手抓饼3块 * sausage 烤肠 3块 * bacon 培根 4块 * egg 鸡蛋2块 * vegetable 青菜 1块 */ public class test { public static void main(string ...strings){ //有一个顾客张三,他想吃手抓饼了,来了一个原味的 customer customera = new customer("张三"); customera.buy(new notelesshandpancake()); //有一个顾客李四,他想吃手抓饼了,他加了一根烤肠 customer customerb = new customer("李四"); customerb.buy(new sausage(new notelesshandpancake())); //有一个顾客王五,他想吃手抓饼了,他加了一根烤肠 又加了培根 customer customerc = new customer("王五"); customerc.buy(new bacon(new sausage(new notelesshandpancake()))); //有一个顾客王五的兄弟,他想吃手抓饼了,他加了培根 又加了烤肠 customer customerc1 = new customer("王五的兄弟"); customerc1.buy(new sausage(new bacon(new notelesshandpancake()))); //有一个顾客赵六,他想吃手抓饼了,他加了一根烤肠 又加了2份培根 customer customerd = new customer("赵六"); customerd.buy(new bacon(new bacon(new sausage(new notelesshandpancake()))));
//有一个顾客 王二麻子,他想吃手抓饼了,特别喜欢吃青菜 来了三分青菜 customer customere = new customer("王二麻子"); customere.buy(new vegetable(new vegetable(new vegetable(new notelesshandpancake()))));
//有一个顾客 有钱人 王大富 来了一个全套的手抓饼 customer customerf = new customer("王大富"); customerf.buy(new egg(new vegetable(new bacon(new sausage(new notelesshandpancake()))))); } }
我们有一个顾客customer类,他拥有buy方法,可以购买手抓饼
手抓饼接口为 handpancake 具体的手抓饼为notelesshandpancake
然后提供了一个配菜类,这个配菜类的行为和手抓饼是一致的,在提供手抓饼的同时还能够增加一些额外的
然后还有四个具体的配菜 培根 香肠 鸡蛋 青菜
运行测试类,会算账的亲们,看看单价是否还对的上?
uml图
懒得画了,idea自动生成的
手抓饼装饰器模式中的根本
上面的代码还是比较清晰的,如果你没办法仔细看进去的话,我们换一种思维方式来思考手抓饼的装饰器模式
你可以这么理解:
你过去手抓饼的摊位那边,你说老板来一个手抓饼,加培根,加鸡蛋
摊主那边是这样子的:
老板负责直接做手抓饼
旁边站着漂亮的老板娘,手里拿着手抓饼的袋子,负责帮你装袋,你总不能直接用手拿饼,对吧
接下来我们说下过程:
老板马上就开始做手抓饼了,做好了之后,老板把手抓饼交给了旁边站着的老板娘
老板娘在给装袋并且交给你之前
把鸡蛋和培根放到了你的手抓饼里面
然后又放到了包装袋子里面
接着递给了你
你说到底是老板娘手里包装好的手抓饼是手抓饼 还是老板做好的热气腾腾的是手抓饼呢?
其实,老板做好的热气腾腾的手抓饼,正是我们上面提供出来的具体的手抓饼
老板娘手里拿着的手抓饼包装袋来包装手抓饼,也是手抓饼,只不过是包装了下,这个就是装饰器的概念
所以装饰器模式还有一个名字 包装器模式(wrapper)
|
解决问题的根本思路是使用组合替代了继承
上面我们也进行了分析,继承会出现类的个数的爆炸式增长
组合,不仅仅动态扩展了类的功能,而且还很大程度上减少了类的个数
不过显然,如果你的装饰类过多,虽说比继承好很多,但是问题还是一样的,都会类过多
根本: 是你还有你 |
我们上面的类的结构中,装饰器包含一个手抓饼对象作为属性,他也实现了手抓饼接口
所以我们说,是你还有你
每次自己返回结果之前,都还会调用自己含有的对象的方法
看下调用流程, 你说它的形式跟递归调用有什么区别?
面向对象中的适配器模式详解
意图
动态的给一个对象添加额外的职责,简单说,动态的扩展职责 就增加功能来说,装饰器模式比生成子类要更加灵活 所以装饰器模式主要解决继承子类爆炸增长的问题 |
装饰器模式中的角色
component | 抽象构建 |
装饰器模式中必然有一个最基本最原始的-> 接口/抽象类 来充当抽象构建 |
抽象的手抓饼 handpancake |
concretecomponent | 具体构建 |
是抽象构建的一个具体实现
你要装饰的就是它
|
具体某家店铺生产的手抓饼 notelesshandpancake |
decorator | 装饰抽象类 |
一般是一个抽象类 实现抽象构建 并且必然有一个private变量指向component 抽象构建 |
配菜抽象类(装饰器) decorator |
concretedecorator | 具体的装饰类 |
必须要有具体的装饰角色 否则装饰模式就毫无意义了 |
具体的配菜(具体的装饰) bacon egg vegetable sausage |
仔细体味下<是你 还有你>
decorator 是component 还有component
|
oop中的一个重要设计原则
类应该对扩展开放,对修改关闭 |
所谓修改就是指继承,一旦继承,那么将会对部分源代码具有修改的能力,比如覆盖方法,所以你尽量不要做这件事情 扩展就是指的组合,组合不会改变任何已有代码,动态得扩展功能 |
装饰器模式优点
装饰类和被装饰类可以独立发展,而不会相互耦合
component类无须知道decorator类,decorator类是从外部来扩展component类的功能,
而decorator也不用知道具体的构件
|
装饰模式是继承关系的一个替代方案
我们看装饰类decorator,不管装饰多少层,他始终是一个component,实现的还是is-a的关系,所以他是继承的一种良好替代方案
|
如果设计得当,装饰器类的嵌套顺序可以任意,比如 一定要注意前提,那就是你的装饰不依赖顺序 |
装饰器模式缺点
装饰器模式虽然从数量级上减少了类的数量,但是为了要装饰,仍旧会增加很多的小类 这些具体的装饰类的逻辑将不会非常的清晰,不够直观,容易令人迷惑 |
装饰器模式虽然减少了类的爆炸,但是在使用的时候,你就可能需要更多的对象来表示继承关系中的一个对象 |
多层的装饰是比较复杂,比如查找问题时,被层层嵌套,不容易发现问题所在 |
装饰器模式使用场景
当你想要给一个类增加功能,然而,却并不想修改原来类的代码时,可以考虑装饰器模式 如果你想要动态的给一个类增加功能,并且这个功能你还希望可以动态的撤销,就好像直接拿掉了一层装饰物 |
装饰器模式的简化变形
装饰器模式是对继承的一种强有力的补充与替代方案,装饰器模式具有良好的扩展性
再次强调,设计模式是一种思维模式,没有固定公式
如果需要的话,可以进行简化
如果省略抽象构建,装饰器直接装饰一个类的话, 那么可以装饰器直接继承这个类 |
如果只有一个具体的装饰器类,那么可以省略掉 decorator concretedecorator 充当了concretedecorator 和 decorator的角色 |
设计模式是作为解决问题或者设计类层级结构时的一种思维的存在,而不是公式一样的存在!
|