模式定义
装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。
模式原理
让装饰器实现被包装类(Concrete Component)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加新功能了。
模式角色
抽象组件(Component):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被装饰对象的行为;
具体组件(ConcreteComponent):实现/继承 Component 的一个具体对象,也即 被装饰对象;
抽象装饰器(Decorator):通用的装饰 ConcreteComponent 的装饰器,其内部必然有一个属性指向 Component抽象组件;其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一个 Component抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许多装饰器,那么我们可以直接省略该类,而直接实现一个 具体装饰器(ConcreteDecorator) 即可);
具体装饰器(ConcreteDecorator):Decorator 的具体实现类,理论上,每个 ConcreteDecorator 都扩展了 Component 对象的一种功能;
装饰模式 角色分配符合设计模式 里氏替换原则,依赖倒置原则,从而使得其具备很强的扩展性,最终满足 开闭原则。
UML类图
模式优点
对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
具体组件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合 “开闭原则”。
模式缺点
使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。
装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
应用场景
- 需要扩展一个类的功能,或给一个类增加附加功能;
- 需要动态地给一个对象增加功能,且这些功能可以再动态地撤销;
- 需要为一批的兄弟类进行改装或加装功能;
应用示例
示例背景
所谓人靠衣装马靠鞍,假设现在要为男人增加穿衣服的功能(穿裤子、穿内衣、穿外套)。
分析
以 装饰模式 的角度来看待上述例子,「人」属于 Component
角色;
「男人」 属于 ConcreteComponent
角色,因此,「男人」 是被装饰对象;
「穿衣服」 是功能扩展,属于 Decorator
角色;
而「穿裤子」,「穿内衣」,「穿外套」 是3个具体功能扩展,均属于 ConcreteDecorator
角色;
使用步骤
抽象组件(Component)
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
具体组件(ConcreteComponent)
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
抽象装饰器(Decorator)
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
具体装饰器(ConcreteDecorator)
TrousersDecorator
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
UnderClothesDecorator
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
OvercoatDecorator
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
客户端调用
1 | package main.java.com.study.designPatterns.decorator.demoOne; |
上面代码中客户是为new Man()
这个IPerson
一件一件的进行衣服试穿,太浪费时间了,完全可以使用装饰器嵌套(因为装饰器接收一个IPerson
,而自己同时也是一个IPerson
,因此完全支持嵌套)模式,这样一次性就穿完,代码如下:
1 | package main.java.com.study.designPatterns.decorator.demoOne; |