Java笔记 · · By/蜜汁炒酸奶

漫谈装饰器模式

定义与目的

装饰器模式(Decorator Pattern)也叫包装器模式(Wrapper Pattern),在不改变原有对象的基础上,动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

原文: Attach additional responsibilities to an object dynaimcally keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.

装饰器模式主要是通过组合代替继承,从而解决继承关系过于复杂的问题。主要作用是为原始类添加增强功能,该功能是和原始功能相关的,这也是判断是否改用装饰器模式的一个重要依据。

笼统来说,代理、桥接、装饰器、适配器这4种模式都可以被称为包装器模式(Wrapper Pattern),即通过 Wrapper 类二次封装原始类,具体区别之后再讲。

应用场景

在买奶茶时,我们看到奶茶可以有很多种类,同样一杯饮品也可以有很多种加料(如珍珠、芋圆等)。这些加料便是一种装饰器模式。

Java的IO类库是装饰器模式的一种典型实现,后期单开章节整理。

《设计模式就该这么学》中提到在代码程序中适用于以下应用场景:

  1. 用于扩展一个类的功能,或者给一个类添加附加功能。
  2. 动态地给一个对象添加功能,这些功能可以动态的被撤销。
  3. 需要为一批平行的兄弟类进行改装或加装功能。

由此更能体会到,装饰器模式用来动态地增强原始类的目的。

装饰器模式的UML类图

装饰器的UML类图如下:

umldecoratorbasea.png

由上图可见,装饰器模式一般包括4个角色:

  1. 抽象组件(Component):可以是接口或者抽象类。充当被装饰类的原始对象,规定了被装饰对象的行为。
  2. 具体组件(ConcreteComponent):一个实现/继承 Component 的具体对象,是我们将要动态地加上新型为的对象,即被装饰对象。
  3. 抽象装饰器(Decorator):装饰器共同实现的接口(或者抽象类),每个装饰器内部都有一个属性指向Component (每个组件都可以单独使用,也可以被装饰器包起来使用)。如果系统中装饰逻辑单一,并不需要实现许多装饰器,可以省略该对象,直接实现具体的装饰器即可。
  4. 具体装饰器(ComponentDecorator):Decorator具体实现类,理论上,每一个具体装饰器都扩展类Component对象的一种功能。装饰器可以加上新的方法。新的行为是通过在旧行为前面或者后面做一些计算来添加的。

由上可以看出装饰器实现原理:

  1. 装饰器与组件(即被装饰对象)拥有相同的超类,使得装饰器与被装饰对象类型一致。
  2. 装饰器构造函数中传入被装饰对象,之后装饰器中在调用被装饰对象行为前后添加相关新功能。

通常装饰器模式采用抽象类,但Java中可以使用接口。需根据实际情况决定具体使用什么,毕竟通常都在努力避免修改现有的代码。

通用写法

先看一下的通用写法:

`/** * 1. 抽象组件 */ public abstract class Component { /** * 方法 */ public abstract void operation(); } /** * 2. 具体组件:实现/继承类,被装饰对象 */ public class ConcreteComponent extends Component{ @Override public void operation() { PrintUtill.println("真正的对象做一些事情"); } } /** * 3. 具体装饰器A */ public class ConcreteComponentA extends Decorator{ /** * 构造方法:传入组件对象 * * @param component */ public ConcreteComponentA(Component component) { super(component); } private void operationOne() { PrintUtill.println("装饰器转发请求前的附加功能>>>>>>>>>>>>A"); } private void operationTwo() { PrintUtill.println("装饰器转发请求后的附加功能>>>>>>>>>>>>A"); } @Override public void operation() { operationOne(); // 添加的新功能 // 可选择性调用父级方法,若不调用,则相当于完全改写了方法,实现新功能 super.operation(); operationTwo(); // 添加的新功能 } } /** * 4. 具体装饰器B,和A类似,只是添加的新行为不同 */ public class ConcreteComponentB extends Decorator{ // ... } /** * 客户端-用于调用测试 */ public class DecoratorClient { public static void main(String[] args) { ConcreteComponentA concreteComponentA = new ConcreteComponentA(new ConcreteComponent()); concreteComponentA.operation(); PrintUtill.printlnRule(); // 嵌套修饰 ConcreteComponentB concreteComponentB = new ConcreteComponentB(concreteComponentA); concreteComponentB.operation(); } }

下面以制作奶茶订单为例,介绍下简单实现:

/** * 1.抽象组件-奶茶 * 首先有个奶茶与装饰器的共同抽象对象 */ public abstract class Boba { // 制作奶茶的抽象方法 public abstract void make(); } /** * 2. 珍珠奶茶(被装饰类) * 新增一种叫珍珠奶茶的奶茶具体实现。 */ public class TapiocaBoba extends Boba{ @Override public void make() { PrintUtill.println("制作珍珠奶茶"); } } /** * 3.抽象装饰器-用于奶茶加料 */ public abstract class BobaDecorator extends Boba{ private Boba boba; public BobaDecorator(Boba boba) { this.boba = boba; } public void make() { // 转发制作请求给奶茶组件对象 boba.make(); } } /** * 4. 奶茶具体装饰器A-奶茶加料-椰果 */ public class CoconutJellyBobaDecorator extends BobaDecorator{ public CoconutJellyBobaDecorator(Boba boba) { super(boba); } private void operationOne() { PrintUtill.println("添加配料:椰果>>>>>>>>>>>>"); } @Override public void make() { super.make(); operationOne(); } } /** * 5.奶茶具体装饰器-奶茶加料-红豆 */ public class RedBeansBobaDecorator extends BobaDecorator{ public RedBeansBobaDecorator(Boba boba) { super(boba); } private void operationOne() { PrintUtill.println("添加配料:红豆>>>>>>>>>>>>"); } @Override public void make() { super.make(); operationOne(); } } /** * 客户端 */ public class BobaClient { public static void main(String[] args) { // 用户A定了一杯椰果珍珠奶茶 CoconutJellyBobaDecorator coconutJellyBoba = new CoconutJellyBobaDecorator(new TapiocaBoba()); // 制作椰果珍珠奶茶 coconutJellyBoba.make(); PrintUtill.printlnRule(); // 用户B想要一杯椰果红豆珍珠奶茶, // 此处为简单演示嵌套装饰,省去了重新创建椰果珍珠奶茶的过程,重用上一个对象。 RedBeansBobaDecorator redBeansBoba = new RedBeansBobaDecorator(coconutJellyBoba); redBeansBoba.make(); } }

依赖继承VS组合

装饰器模式通过组合代替继承,主要作用是动态地为原始类添加增强功能。但通过上面的UML图以及实现代码可见,装饰器 ComponentDecorator 扩展自抽象组件 Component ,这也是用到了继承。但这里的继承是为了得到正确的类型(也即“类型匹配”),而不是继承它的行为(即利用继承获得行为)。

一般情况下依赖继承意味着继承行为,类的行为只能在编译时静态决定。行为不是来自超类,就是子类覆盖后的版本。每当新增一个行为,都需修改现有代码(如,在奶茶中增加新配料,则可能需要每个类中都增加一个新配料对应属性)。

使用组合,可以在任何时候实现新的装饰器增加新的行为,从这方面讲是“动态地”(如,在奶茶中增加新配料,无需修改之前的代码,只需在新的装饰器中增加相应属性以及相关逻辑即可)。

总结

由上我们可以大致了解:

  1. 装饰器与被装饰对象拥有相同的超类。如此,在任何需要原始类(被装饰类)的场合,都可以用装饰过的对象代替。
  2. 可以用一个或多个装饰器(嵌套装饰)包装一个对象。
  3. 装饰器类可以在委托被装饰类的行为之前/之后,加上自己的行为,以达到特定的目的(主要作用)。
  4. 对象可以在任何时候被修饰,因此可以在运行时动态地/不限量的用喜欢的装饰器来装饰对象。

与代理模式区别

  1. 装饰器模式是增强原始类自身功能,主体对象是被装饰类。
  2. 代理模式强调访问控制,附加的是与原始类自身功能无关的功能。代理类可以决定对功能进行扩展、缩减甚至消失(不调用真实对象的相应方法),主体对象是代理类。

装饰器模式的优点

  1. 对继承的补充,通过组合,比继承更灵活。在不改变原有对象的情况下,动态扩展对象功能。
  2. 通过使用不同的装饰器以及这些类的排列组合(嵌套装饰),可以实现不同的效果。
  3. 装饰器模式完全遵循开闭原则。

装饰器模式的缺点

  1. 会导致设计中出现许多小对象(也会出现更多代码、更多的类),若过度使用会让程序变得更复杂。
  2. 状态装饰在多层装饰(即嵌套装饰)时会更复杂。

参考资料:

  1. 设计模式之美
  2. 《Head First 设计模式》
  3. 《设计模式就该这么学》
  4. 《Java设计模式及实践》

JK_DesignPattern.png

评论已关闭

example
C
蜜汁炒酸奶

当前处于试运行期间,可能存在不稳定情况,敬请见谅。

欢迎点击此处反馈访问过程中出现的问题