我正在学习Decorator Pattern
。它是非常强大的模式,非常有用。模式的目标很简单,在运行时向对象添加(扩展)行为,而无需通过组合和委托重新编译源代码,因此为扩展行为提供了子类化的替代方法。但是,我读到了这种模式的一个主要缺点,并不能完全理解它。以下是声明:
"人们有时会接受一些依赖的客户端代码 特定类型和介绍装饰而不经过深思熟虑 一切。现在,关于我的一件好事是你通常可以 透明地插入装饰器,客户端永远不必知道它 处理装饰者。但就像我说的,一些代码依赖于 特定类型,当你开始介绍装饰,繁荣!坏 事情发生了。"
取自"掌握第一个设计模式"
作者的意思是"依赖于特定类型"。如果可能,请使用现实世界和简单的例子
答案 0 :(得分:6)
有时您的代码会像java中那样进行类型检查
a instanceof B
甚至
a.getClass == B.class
这种检查中断
如果a不再是B而是装饰者D缠绕在A
上举一个具体的例子:
I b = new B; // I is an interface implemented by B
b = D(b); // the decorator D implements I as well
doSomething(b);
doSomething定义如下:
doSomething(I i){
if i instanceof B
doThis();
else
doThat();
}
通过引入装饰器D,行为从doThis()
更改为doThat()
一个真实世界的例子,这种情况发生在Hibernate上。它在某些情况下生成代理,这些代理提供额外的Hibernate特定行为。如果使用这些实体的代码确实如上所述进行检查,则它将根据创建的实体的方式而中断。实际上可能有效的实例,但getClass()变体肯定会失败。
答案 1 :(得分:1)
从Jens提供的优秀答案中解脱出来;它完美地代表了作者和许多其他程序员的观点。
然而,这种观点已经过时且具有教育意义。
首先,经常引用Jens使用的示例,但它不是装饰器的可接受正确的构造。一些正确的装饰器或包装器结构如下:
使用合成继承:
B的实例
以这种方式组合D与a的任何比较仍然有效
Decorator D扩展了B并实现了接口C(新功能)
a可以指定为D,但仍保持功能
使用界面封装:
接口C有一个装饰器D(新功能),其方法M包裹D
A扩展B和实施C
A也是B的一个实例,并且仍然有效
使用控件反转
这是类似的,但如果您熟悉IoC
,这会有点不太明显A扩展B,可以构造或注入D,其功能由方法M包裹。
A的实例动态绑定到需要B
的实例此绑定适用于B
的所有情况d的实例在运行时注入,其中A
需要它的功能在大多数情况下,装饰器实现中的缺陷是糟糕的设计或实现的错误。进一步的装饰器一直用于高可用性应用。
真实世界的例子,您可能会使用的每个GUI,WYSWIG编辑器,Web浏览器和IDE,以及每个编程语言解释器,游戏引擎,照片编辑器,移动或Web应用程序,哦和CSS以及SVG渲染,实际上几乎任何现代渲染管道都是装饰器模式的特例。所以我想我会说,尊重装饰师。