设计模式之装饰者模式(Decorator Pattern)
一.什么是装饰者模式?
装饰者模式能够完美实现“对修改关闭,对扩展开放”的原则,也就是说我们可以在不修改被装饰者的前提下,扩展被装饰者的功能。
再来看看我们的文件操作代码:
1
|
InputStream in = new BufferedInputStream( new FileInputStream(file)); |
被“包裹”在最内层的InputStream对象是new FileInputStream(file),是基本的文件输入流,用BufferedInputStream对象来扩展它的功能,甚至我们还可以这样:
P.S.注意上面用到的动词——“包裹”,这就是装饰者模式的核心了
1
|
InputStream in = new LineNumberInputStream( new BufferedInputStream( new FileInputStream(file))); |
继续添加一层新的装饰LineNumberInputStream,以扩展获取行号的功能,当然,我们还可以扩展更多的功能,只要继续添加新的装饰就好了
回过头来想想,我们给FileInputStream添加了一层层装饰,获得了一个个功能,在此过程中,我们实现了功能的动态扩展,但并没有修改被装饰者FileInputStream的任何东西。
这就是所谓的“对修改关闭,对扩展开放”原则。
二.举个例子
假设我们要开店卖Milk,可选的配料有摩卡Mocha(巧克力味),咖啡Coffee,冰水IceWater,当然,如果卖得好的话我们还打算引进新的饮料(Orange、Yoghurt等等)以及新的配料(Salt。。玩笑)
Milk本身有价格,并且会在节假日打折,各种不同的配料价格也不同,当然,我也可以点一杯加双份IceWater的Milk。。
-------
最容易想到的解决方案是:
定义一个Milk类,包含很多属性,例如hasMocha, hasCoffee, hasIceWater(用来表示已添加的配料),还需要discount属性(用来表示折扣信息)、cost属性(用来表示价格)
这就好了吗?不,除此之外我们还需要MochaNum, CoffeeNum, IceWaterNum(用来表示配料的份数,重口味顾客需要双份或者更多的配料。。)
问题解决了,可是这样做真的好吗?
考虑以下这些情况:
1.引进一种新的饮料Orange(我们需要定义一个Orange,几乎没有复用的部分,从零开始。。或者,我们可以定义一个Beverage基类,把饮料共有的部分放进去)
2.引进一种新的配料Salt(我们必须修改Milk类,添加hasSalt, SaltNum属性以满足加盐Milk需求。。)
。。。
现在看来我们的解决方案很差,不能适应任何变化,要扩展功能就可能必须修改已有的封装好的代码,而且还存在一个性能上的问题:计算饮料价格部分需要大量的if...else...结构,使得我们的代码很臃肿,且难以复用(不同饮料配料可能不同,计算价格的方法也不同)
-------
是时候尝试装饰者模式了
首先,因为被装饰者与装饰者必须要具有相同的超类(暂不解释为什么),所以,我们定义下面的Beverage基类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
package DecoratorPattern; /** * @author ayqy * 定义Beverage超类,所有具体Beverage和Ingredient都必须扩展自此类 * */ public abstract class Beverage { String desc = "Unknown Beverage" ; //定义饮料相关描述信息 float cost; //定义饮料的价格 public abstract float getCost(); //定义cost方法返回该饮料的价格,子类必须实现此方法 public String getDesc(){ return desc; } } |
有了Beverage就可以开始定义被装饰者——Milk:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
package DecoratorPattern; /** * @author ayqy * 定义具体Beverage:Milk类 * */ public class Milk extends Beverage{ float discount = 1 ; //定义折扣,节假日Milk可能会打折(默认不打折) public float getDiscount() { return discount; } public void setDiscount( float discount) { this .discount = discount; } public Milk(){ cost = 4 .5f; //初始化Milk的价格 desc = "Milk" ; //初始化描述信息 } @Override public float getCost(){ return discount * cost; //返回打折后的价格 } } |
接下来是装饰者,因为装饰者具有一些不同于Beverage的特性,所以我们对其进行抽象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package DecoratorPattern; /** * @author ayqy * 定义Ingredient佐料类,继承自Beverage(在装饰者模式中,装饰者与被装饰者必须具有相同的超类) * */ public abstract class Ingredient extends Beverage{ Beverage beverage; //需要添加该佐料的饮料 @Override public String getDesc() { return "(" + desc + ")" + beverage.getDesc(); //佐料的描述应当带上括号,以区别佐料与饮料 } @Override public float getCost() { return cost + beverage.getCost(); //配料没有折扣,直接返回其价格 + 饮料价格 } //在此添加其它Ingredient不同于Beverage的属性与行为 } |
注意上面的getDesc与getCost方法,我们把计算价格与生成描述信息的责任完全委托给方法调用机制了,以至于代码是如此的简洁。。
下面定义具体配料——IceWater,Coffee,Mocha:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package DecoratorPattern; /** * @author ayqy * 定义配料IceWater冰水 * */ public class IceWater extends Ingredient{ public IceWater(Beverage bev) { cost = 0 .5f; desc = "IceWater" ; beverage = bev; } } |
一切准备就绪,我们的Milk小店可以开张了。。
三.效果示例
先定义一个测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package DecoratorPattern; public class Test { public static void main(String[] args){ Beverage bev; System.out.println( "做一杯加摩卡加咖啡的Milk。。" ); bev = new Milk(); //先做一杯Milk bev = new Mocha(bev); //添加Mocha bev = new Coffee(bev); //添加Coffee System.out.println(bev.getDesc() + " " + bev.getCost() + "¥" ); System.out.println( "做一杯加双份冰水双份摩卡的咖啡Milk。。" ); bev = new Milk(); //重新做一杯Milk bev = new IceWater(bev); //添加冰水 bev = new IceWater(bev); //添加冰水 bev = new Mocha(bev); //添加Mocha bev = new Mocha(bev); //添加Mocha bev = new Coffee(bev); //添加Coffee System.out.println(bev.getDesc() + " " + bev.getCost() + "¥" ); //当然也可以这样写: Beverage bev = new Coffee(new Mocha(new Milk())); //对比我们熟悉的方法链: InputStream in = new BufferedInputStream(new FileInputStream(file)); } } |
结果示例:
-------
效果不错吧,再考虑之前的扩展问题:
1.引进一种新的饮料Orange(我们需要定义一个Orange类继承自Beverage基类,可以复用Beverage基类中已有的部分,如果还不满意,当然也可以抽象出一个“具体饮料类ConcreteBeverage”,让Milk等其它饮料在此基础上扩展)
2.引进一种新的配料Salt(我们不必对Milk类做任何修改,只需要实现一种Salt配料,继承自Ingredient类就好了)
。。。
发现装饰者模式的优点了吗?
那么是时候泼一盆冷水了。。
四.装饰者模式的优缺点
缺点其实显而易见——你见过这么长的代码吗?
1
|
XObject o = new XDecorator( new XXDecorator( new XXXDecorator( new XXXXDecorator()))); |
嗯,它只是给被装饰对象做了三次功能扩展而已,当然,还可以更多。。也就意味着可以更长
而且,我们在使用时创建了很多小对象,就像这样:
1
2
3
4
5
6
|
bev = new Milk(); //重新做一杯Milk bev = new IceWater(bev); //添加冰水 bev = new IceWater(bev); //添加冰水 bev = new Mocha(bev); //添加Mocha bev = new Mocha(bev); //添加Mocha bev = new Coffee(bev); //添加Coffee |
让一个不熟悉装饰者模式的人来读上面的代码,他能很快弄明白吗?
注意,上面的代码就解释了开篇提到的动词——“包裹”,对吗?
-------
优点:
除了上面提到的动态扩展优点,还有一个更重要的优点就是前面提到的getDesc与getCost方法
没错,我们可以利用这种调用机制来完成我们的操作(在装饰动作前或者装饰动作后添加我们的自定义操作就好了,例子里其实属于在装饰动作后添加操作),我们很轻易的达到了类似于递归的效果
这也就解释了“为什么装饰者与被装饰者要具有相同的超类?”,还需要更多一点的解释:
有一种设计原则是“多用组合,少用继承”,这里我们好像违背了这个原则吧
其实并没有违背原则,装饰者模式中的继承是为了获得类型的匹配,而不是为了利用继承来扩展类的行为,而“多用组合,少用继承”原则省略掉的前提条件是“(当我们需要扩展类的行为时)多用组合,少用继承”
<声明>作者水平有限 错误在所难免 欢迎指正</声明>
<邮箱>835412398@qq.com 交流方可进步</邮箱>
设计模式之装饰者模式(Decorator Pattern)的更多相关文章
- 设计模式学习--装饰者模式(Decorator Pattern)
概念: 装饰者模式(Decorator Pattern): 动态地将功能添加到对象,相比生成子类更灵活,更富有弹性. 解决方案: 装饰者模式的重点是对象的类型,装饰者对象必须有着相同的接口,也也就是有 ...
- python 设计模式之装饰器模式 Decorator Pattern
#写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...
- 23种设计模式之装饰器模式(Decorator Pattern)
装饰器模式(Decorator Pattern) 允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰类,用来包 ...
- C#设计模式之装饰者模式(Decorator Pattern)
1.概述 装饰者模式,英文名叫做Decorator Pattern.装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 2 ...
- c#设计模式之装饰器模式(Decorator Pattern)
引子 在面向对象语言中,我们常常会听到这样一句话:组合优于继承.那么该如何去理解这句话呢? 下面我将以游戏装备为模型用简单的代码去展示它 先创建一个装备的抽象类,然后创建刀枪2个具体的业务子类 pub ...
- 【UE4 设计模式】装饰器模式 Decorator Pattern
概述 描述 动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活.是一种对象结构型模式. 套路 抽象构件(Component) 具体构 ...
- 浅谈设计模式--装饰者模式(Decorator Pattern)
挖了设计模式这个坑,得继续填上.继续设计模式之路.这次讨论的模式,是 装饰者模式(Decorator Pattern) 装饰者模式,有时也叫包装者(Wrapper),主要用于静态或动态地为一个特定的对 ...
- 设计模式 - 装饰者模式(Decorator Pattern) Java的IO类 用法
装饰者模式(Decorator Pattern) Java的IO类 用法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26716 ...
- 设计模式 - 装饰者模式(Decorator Pattern) 具体解释
装饰者模式(Decorator Pattern) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26707033 装饰者 ...
- 设计模式(三):“花瓶+鲜花”中的装饰者模式(Decorator Pattern)
在前两篇博客中详细的介绍了"策略模式"和“观察者模式”,今天我们就通过花瓶与鲜花的例子来类比一下“装饰模式”(Decorator Pattern).在“装饰模式”中很好的提现了开放 ...
随机推荐
- [Reactive Programming] Using an event stream of double clicks -- buffer()
See a practical example of reactive programming in JavaScript and the DOM. Learn how to detect doubl ...
- linux下ntfs硬盘的加载
问题: # mount –t ntfs /dev/sdb1 /mnt/ mount: unknown filesystem type ‘ntfs’ 这是由于Cent ...
- 用 Qt 中的 QDomDocument类 处理 XML 文件(上)
我们可以看到,如果所要读取的XML文件不是很大,采用DOM读取方法还是很便捷的,由于我用的也是DOM树读取的方法,所以,本文所介绍的也主要是基于DOM的方法读取. 根据常用的操作,我简单的把对XM ...
- spring mvc DispatcherServlet详解之二---request通过Controller获取ModelAndView过程
整个spring mvc的架构如下图所示: 上篇文件讲解了DispatcherServlet通过request获取控制器Controller的过程,现在来讲解DispatcherServletDisp ...
- iOS UIKit:view
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css); @import url(/ ...
- 偶遇问题 - - JavaScript 取消链接默认行为问题
今天在测试<JavaScript DOM编程艺术(第2版)>中第69页代码时,遇到了问题.本来预期效果应该是点击链接后不跳转当前页面,而是另外弹出有个窗口.但结果却是页面跳转了.代码如下图 ...
- 原生JS+tween.js模仿微博发布效果
转载请注明出处:http://www.cnblogs.com/zhangmingze/p/4816865.html 1.先看效果吧,有效果才有动力: 2.html结构: <!DOCTYPE ht ...
- 国人编写的开源 .net Ioc 框架——My.Ioc 简介
My.Ioc 是作者开发的一款开源 IoC/DI 框架,下载地址在此处.它具有下面一些特点: 高效 在实现手段上,My.Ioc 通过使用泛型.缓存.动态生成代码.延迟注册.尽量使用抽象类而非接口等方式 ...
- 如何在安卓/data(而不是/data/data)目录下进行文件的读写操作
分析:Android默认是无法直接操作/data目录的,只能读写程序自己的私有目录,也就是/data/data/package name/下,默认只能操作这个目录下的文件,也就是我们想直接读写/dat ...
- ContextSwitchDeadlock was detected Message(读取注册表时出现).
google的时候,在StackOverflow中得到个暂时解决的方法: http://stackoverflow.com/questions/2797677/contextswitchdeadloc ...