简介
Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality.
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相比生成子类更为灵活。
装饰模式(Decorator Pattern) 也称为 包装模式(Wrapper Pattern),是结构型设计模式之一,其使用一种对客户端透明的方式来动态地扩展对象的功能,经常作为 继承 的一种替代方案之一。
装饰模式 的核心:功能扩展。
使用 装饰模式 可以透明且动态地扩展类的功能。
主要解决
装饰模式 主要用于透明且动态地扩展类的功能。其实现原理为:让装饰器实现被包装类(Concrete Component)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加新功能了。而且由于装饰器与被包装类属于同一类型(均为Component),且构造函数的参数为其实现接口类(Component),因此 装饰模式 具备嵌套扩展功能,这样我们就能使用 装饰模式 一层一层的对最底层被包装类进行功能扩展了。
优缺点
优点
- 动态扩展类功能,比类继承灵活,且对客户端透明;
- 继承关系的一种替代方案。相比与类继承的父子关系,装饰模式 更像是一种组合关系(is-a);
- 可以对同一个被装饰对象进行多次装饰,创建出不同行为的复合功能;
缺点
- 多层装饰比较复杂(灵活的同时会带来复杂性的增加);
- 装饰嵌套过多,会产生过多小对象(每个装饰层都创建一个相应的对象);
- 装饰嵌套过多,易于出错,且调试排查比较麻烦(需要一层一层对装饰器进行排查,以确定是哪一个装饰层出错);
使用场景
- 需要扩展一个类的功能,或给一个类增加附加功能;
- 需要动态地给一个对象增加功能,且这些功能可以再动态地撤销;
- 需要为一批的兄弟类进行改装或加装功能;
模式讲解
首先看下 装饰模式 的通用 UML 类图:
从 UML 类图中,我们可以看到,装饰模式 主要包含四种角色:
- 抽象组件(Component):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被装饰对象的行为;
- 具体组件(ConcreteComponent):实现/继承 Component 的一个具体对象,也即 被装饰对象;
- 抽象装饰器(Decorator):通用的装饰 ConcreteComponent 的装饰器,其内部必然有一个属性指向 Component抽象组件;其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一个 Component抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许多装饰器,那么我们可以直接省略该类,而直接实现一个 具体装饰器(ConcreteDecorator) 即可);
- 具体装饰器(ConcreteDecorator):Decorator 的具体实现类,理论上,每个 ConcreteDecorator 都扩展了 Component 对象的一种功能;
总结:装饰模式 角色分配符合设计模式 里氏替换原则,依赖倒置原则,从而使得其具备很强的扩展性,最终满足 开闭原则。
代码展示
举例:所谓人靠衣装马靠鞍,假设现在我们想要为 人 增加穿衣服的功能,应该怎样实现呢?
要求:使用 装饰模式 分别为 男人 添加 穿裤子,穿内衣,穿外套 的功能。
分析: 以 装饰模式 的角度来看待上述例子,人 属于 Component 角色;男人 属于 ConcreteComponent 角色,因此,男人 是被装饰对象;穿衣服 是功能扩展,属于 Decorator 角色;而 穿裤子,穿内衣,穿外套 是3个具体功能扩展,均属于 ConcreteDecorator 角色;
按以上分析,可以得到如下代码:
class Client {
public static void main(String[] args) {
IPerson person = new Man();
person.dress();
System.out.println("----------------------");
System.out.println("增加裤子适配器");
person = new TrousersDecorator(person);
person.dress();
System.out.println("----------------------");
System.out.println("再增加内衣适配器");
person = new UnderClothesDecorator(person);
person.dress();
System.out.println("----------------------");
System.out.println("再增加外套适配器");
person = new OvercoatDecorator(person);
person.dress();
}
// 抽象组件(Component)
interface IPerson {
void dress();
}
// 具体组件(ConcreteComponent),即被修饰者
static class Man implements IPerson {
@Override
public void dress() {
System.out.println("穿了内裤!");
}
}
// 抽象适配器(Decorator),接收一个具体的Component,本身也是一个Component
static abstract class ClothesDecorator implements IPerson {
protected IPerson mPerson;
// 构造方法强制子类构造必须传入一个IPerson
public ClothesDecorator(IPerson person) {
this.mPerson = person;
}
@Override
public void dress() {
this.mPerson.dress();
}
}
//具体装饰器(ConcreteDecorator):裤子装饰器
static class TrousersDecorator extends ClothesDecorator {
public TrousersDecorator(IPerson person) {
super(person);
}
@Override
public void dress() {
super.dress();
this.dressTrousers();
}
private void dressTrousers() {
System.out.println("穿上裤子了!");
}
}
//具体装饰器(ConcreteDecorator):内衣装饰器
static class UnderClothesDecorator extends ClothesDecorator {
public UnderClothesDecorator(IPerson person){
super(person);
}
@Override
public void dress() {
super.dress();
this.dressUnderClothes();
}
private void dressUnderClothes(){
System.out.println("穿上内衣了!");
}
}
//具体装饰器(ConcreteDecorator):外套装饰器
static class OvercoatDecorator extends ClothesDecorator {
public OvercoatDecorator(IPerson person){
super(person);
}
@Override
public void dress() {
super.dress();
this.dressOvercoat();
}
private void dressOvercoat(){
System.out.println("穿上外套了!");
}
}
}
上面的代码中Man
这个IPerson
本身只穿了件内裤,衣裳不整,不能入目-_-,因此我们使用装饰器分别为其增加了穿裤子,内衣,外套的功能,最终完成了穿整套衣服的功能。
代码运行结果如下:
穿了内裤!
----------------------
增加裤子适配器
穿了内裤!
穿上裤子了!
----------------------
再增加内衣适配器
穿了内裤!
穿上裤子了!
穿上内衣了!
----------------------
再增加外套适配器
上面代码中客户是为new Man()
这个IPerson
一件一件的进行衣服试穿,太浪费时间了,我们完全可以使用装饰器嵌套(因为装饰器接收一个IPerson
,而自己同时也是一个IPerson
,因此完全支持嵌套)模式,这样一次性就穿完,代码如下:
class Client {
public static void main(String[] args) {
IPerson person = new OvercoatDecorator(new UnderClothesDecorator(new TrousersDecorator(new Man())));
person.dress();
}
结果如下:
穿了内裤!
穿上裤子了!
穿上内衣了!
穿上外套了!