首页 > 学院 > 开发设计 > 正文

面试者眼中的装饰者模式

2019-11-09 14:30:13
字体:
来源:转载
供稿:网友

引言

  昨天面试中又问到装饰者模式,这次不像一年前那样一头雾水什么都说不上来。但是回答得也很一般,我说就像JDK中IOStream一样,如果使用继承,会导致非常多的子类,并且java是单继承,非常不方便;而给对象添加上不同的装饰物,但是其原本的调用风格不变云云。其实我脑子闪过的是这样的代码

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("fileName")));

  当面试者要求我画出装饰者模式的类图时,我说忘记了,画不出来。确实,看过很多遍装饰者模式的书籍、技术帖子,但是过一段时间又容易忘记。最根本原因是没有理解到位,求大求全,各种模式一一浏览,却没有深入使用。因此本文呼之欲出,不为教导他人,更多的是记录自己遇到的问题,记录自己收集整理并分析资料和加深理解的过程。

装饰者模式UML类图

  查询资料后,很容易画出装饰者模式的UML类图,如下。装饰者模式的书面定义是:“动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。”——语出《Android源码设计模式解析与实战》   想起易中天先生的话,“那么我们不禁要问”,谁是那个对象?谁给它增加了额外的职责?继续翻书、刷帖子,发现:那个对象就是图中的ConcreteComponent,它原本的职责是Operate(),ConcreteDecoratorA给它增加了operateA1()、operateA2()两个额外的职责。看到这里,你以为我要看图说话,根据UML类图给自己解释装饰者模式的原理了。

这里写图片描述

面向接口编程

  No,我想到另一个问题。为什么所有的书籍,所有的技术博客都直接告诉你,装饰者模式整个组成就是这样?为什么一上来你就知道,有一个抽象类或者接口叫Component,然后有一个实现类ConcreteComponent?或者说,装饰者模式的应用中,是先有什么,后有什么?许许多多的技术博客都会从“装饰”本身这个词入手,将人穿衣装饰为例子进行讲解。也就是首先有个抽象类Person,该类有个抽象方法dress(),然后有个子类Xiaoming,继承Person实现dress()方法云云。   但是正常人的思维顺序,或者说没有接触过装饰者模式深圳不熟悉设计模式的人,他的第一反应是:首先,我有一个对象XXX,他有个dress()的动作,后来根据业务需要,我要想办法给XXX增加一些职责,给这个人增加装饰。然后人的第一反应是继承,XXX1,XXX2。。。有多少种穿衣打扮方式就有多少种子类。于是,这种方式明显不合适。我们想到要用面向对象的封装和多态来解决这个问题。首先要引入的是面向接口的编程,就是我们要经常地想到,要将一个业务的定义和实现进行分离。比如人的穿衣这件事,分两个部分来完成。

public interface iperson{ public void dress();}public class PersonImpl implements IPerson{ @Override public void dress(){ ...//方法体实现 } //其他方法}

第一个是定义一个接口(或者抽象类)IPerson,将人的共通的特性放在里面,而某些特定的特性放在其实现类PersonImpl中。怎么样,是不是很有Java EE 搭建框架的感觉?这样做多了一个类,代码增加了,但是大有裨益。一个显而易见的好处是,当你在某处拿到一个IPerson引用时,你的IDE的代码提示功能只会提示你IPerson里面的方法,不会提示你PersonImpl中的所有方法,这在API级别屏蔽了一些细节。   于是我们顺理成章地理解了,最初的最初,我们只有左上角那两个蓝色的类。一个抽象类,一个是这个类的具体实现,至于叫什么并不重要。解决了本体(人)的问题,接下来关注装饰者(衣物)部分。同样的道理,装饰物有很多,会抽取出共通的部分,放到基类中。实际使用时并不会直接使用基类,因此上面的UML类图中的Decorator通常也定义为抽象类,不让它实例化。装饰物Decorator需要继承本体抽取出来的抽象类Component(或者实现Component接口),同时又保存一个对本体的引用。听起来很像代理模式?是的,很像很像。只是代理模式是在代理类中实例化被代理者对象,而装饰者模式是在构造函数中传入本体对象作为引用;代理模式重点在隐藏被代理者的具体内容,而装饰者重点在制造出一系列可插拔的装饰物并且不改变本体的行为 ,关注点不同。

装饰者模式的应用

装饰者模式在JDK中的应用

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("fileName")));

  回到引言中提到的代码,BufferedInputStream相当于装饰者模式UML类图中的ConcreteDecoratorA这个角色,而FileInputStream相当于ConcreteComponent角色。Decorator角色的正确理解是:它并不是纯粹的装饰者,它是装饰本体后的对象。按人-衣物的例子来说,Decorator表示穿上某件衣服饰品的人,而不是饰品本身,这和实际生活有细微的差别。实际生活中,装饰品可以单独存在。你穿,或者不穿,衣服饰品就在那,不多不少。而装饰者模式中,装饰者这个角色必须要在构造函数中传入一个本体,即它所装饰的东西,要不然它没有什么实际意义。ConcreteComponent是你,ConcreteDecoratorA是穿上衣服A的你,而不是A那件衣服本身。ConcreteDecoratorA是为“可穿衣服的的东西”量身定做,它可以给你穿,给路人甲穿,甚至给狗穿。但是不能给树穿给天空穿,尽管实际生活中可以。   

装饰者模式在Android源码中的应用

  当我参照资料画出下面的UML类图,我被自己吓了一跳!我们在Android开发中每天都见到的Activity原来是个装饰者,其装饰的本尊是ContextImpl,那些常用的方法,诸如startActivity,是在ContextImpl中具体实现的。而且不止有Activity,application和Service都是ContextImpl的装饰者,因此你可以在Application中启动一个Activity,也可以在Service中启动。 这里写图片描述   Context这个Android开发中的上帝对象,在本文中就不展开论述。文末附上装饰者模式UML类图中的部分类的源码,以供参考。   

public abtract class Decorator extends Component{ PRivate Component component; public Decorator(Component component){ this.component=component; } @Override public void operate(){ component.operate(); }}public class ConcreteDecoratorA extends Decorator{ ... @Override public void operate(){ operateA1(); super.operate(); operateA12(); } ...
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表