定义与目的
装饰器模式(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.
装饰器模式主要是通过组合代替继承,从而解决继承关系过于复杂的问题。主要作用是为原始类添加增强功能,该功能是和原始功能相关的,这也是判断是否改用装饰器模式的一个重要依据。
应用场景
在买奶茶时,我们看到奶茶可以有很多种类,同样一杯饮品也可以有很多种加料(如珍珠、芋圆等)。这些加料便是一种装饰器模式
《设计模式就该这么学》中提到在代码程序中适用于以下应用场景:
- 用于扩展一个类的功能,或者给一个类添加附加功能。
- 动态地给一个对象添加功能,这些功能可以动态的被撤销。
- 需要为一批平行的兄弟类进行改装或加装功能。
装饰器模式的UML类图
装饰器的UML类图如下:
由上图可见,装饰器模式一般包括4个角色:
- 抽象组件(Component):可以是接口或者抽象类。充当被装饰类的原始对象,规定了被装饰对象的行为。
- 具体组件(ConcreteComponent):一个实现/继承 Component 的具体对象,是我们将要动态地加上新型为的对象,即被装饰对象。
- 抽象装饰器(Decorator):装饰器共同实现的接口(或者抽象类),每个装饰器内部都有一个属性指向Component (每个组件都可以单独使用,也可以被装饰器包起来使用)。如果系统中装饰逻辑单一,并不需要实现许多装饰器,可以省略该对象,直接实现具体的装饰器即可。
- 具体装饰器(ComponentDecorator):Decorator具体实现类,理论上,每一个具体装饰器都扩展类Component对象的一种功能。装饰器可以加上新的方法。新的行为是通过在旧行为前面或者后面做一些计算来添加的。
由上可以看出装饰器实现原理:
- 装饰器与组件(即被装饰对象)拥有相同的超类,使得装饰器与被装饰对象类型一致。
- 装饰器构造函数中传入被装饰对象,之后装饰器中在调用被装饰对象行为前后添加相关新功能。
通常装饰器模式采用抽象类,但Java中可以使用接口。需根据实际情况决定具体使用什么,毕竟通常都在努力避免修改现有的代码。
通用写法
先看一下的通用写法:
`/*** 1. 抽象组件*/public abstract class Component {/*** 方法*/public abstract void operation();}/*** 2. 具体组件:实现/继承类,被装饰对象*/public class ConcreteComponent extends Component{@Overridepublic 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");}@Overridepublic 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{@Overridepublic 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("添加配料:椰果>>>>>>>>>>>>");}@Overridepublic void make() {super.make();operationOne();}}/*** 5.奶茶具体装饰器-奶茶加料-红豆*/public class RedBeansBobaDecorator extends BobaDecorator{public RedBeansBobaDecorator(Boba boba) {super(boba);}private void operationOne() {PrintUtill.println("添加配料:红豆>>>>>>>>>>>>");}@Overridepublic 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 ,这也是用到了继承。但这里的继承是为了得到正确的类型(也即“类型匹配”),而不是继承它的行为(即利用继承获得行为)。
一般情况下依赖继承意味着继承行为,类的行为只能在编译时静态决定,。行为不是来自超类,就是子类覆盖后的版本。每当新增一个行为,都需修改现有代码(如,在奶茶中增加新配料,则可能需要每个类中都增加一个新配料对应属性)。
使用组合,可以在任何时候实现新的装饰器增加新的行为,从这方面讲是“动态地”(如,在奶茶中增加新配料,无需修改之前的代码,只需在新的装饰器中增加相应属性以及相关逻辑即可)。
总结
由上我们可以大致了解:
- 装饰器与被装饰对象拥有相同的超类。如此,在任何需要原始类(被装饰类)的场合,都可以用装饰过的对象代替。
- 可以用一个或多个装饰器(嵌套装饰)包装一个对象。
- 装饰器类可以在委托被装饰类的行为之前/之后,加上自己的行为,以达到特定的目的(主要作用)。
对象可以在任何时候被修饰,因此可以在运行时动态地/不限量的用喜欢的装饰器来装饰对象。
与代理模式区别
装饰器模式是增强原始类自身功能,主体对象是被装饰类。
代理模式强调访问控制,附加的是与原始类自身功能无关的功能。代理类可以决定对功能进行扩展、缩减甚至消失(不调用真实对象的相应方法),主体对象是代理类。
装饰器模式的优点
对继承的补充,通过组合,比继承更灵活。在不改变原有对象的情况下,动态扩展对象功能。
- 通过使用不同的装饰器以及这些类的排列组合(嵌套装饰),可以实现不同的效果。
-
装饰器模式的确定
会导致设计中出现许多小对象(也会出现更多代码、更多的类),若过度使用会让程序变得更复杂。
- 状态装饰在多层装饰(即嵌套装饰)时会更复杂。
