简介
装饰器模式主要解决的问题是,如果使用子类继承的方式扩展一个类,随着扩展功能的增多,子类会非常膨胀,包括子类的数量或子类的方法数。
装饰器模式其核心还是“用组合替代继承”,但是相对于普通的组合关系,装饰器模式还有两个比较特殊的地方:
- 装饰器类和原始类继承同样的父类,这样就可以对原始类“嵌套”多个装饰器类
- 装饰器类是对功能的加强,这也是装饰器模式应用场景的一个重要特点
具体实现
以图书馆的图书和借阅者为例,在已定义了实体类的基础上,增加借书计数的功能。
定义图书馆的抽象类:
public abstract class Library { | |
// 借书 | |
public abstract void borrowBook(); | |
// 还书 | |
public abstract void returnBook(); | |
} |
定义具体图书馆类继承抽象图书馆类:
public class ConcreteLibrary extends Library { | |
@Override | |
public void borrowBook() { | |
// 实现类借书 | |
System.out.println("ConcreteLibrary borrowBook"); | |
} | |
@Override | |
public void returnBook() { | |
// 实现类还书 | |
System.out.println("ConcreteLibrary returnBook"); | |
} | |
} |
定义抽象装饰器类继承抽象图书馆类:
public abstract class LibraryDecorator extends Library { | |
protected Library library; | |
public LibraryDecorator(Library library) { | |
this.library = library; | |
} | |
@Override | |
public void borrowBook() { | |
this.library.borrowBook(); | |
} | |
@Override | |
public void returnBook() { | |
this.library.returnBook(); | |
} | |
} |
再定义具体的装饰器类继承抽象装饰器类:
public class CountingLibrary extends LibraryDecorator { | |
private int counter = 0; | |
public CountingLibrary(Library library) { | |
super(library); | |
} | |
@Override | |
public void borrowBook() { | |
this.library.borrowBook(); | |
this.counter++; | |
this.printCounter(); | |
} | |
@Override | |
public void returnBook() { | |
this.library.returnBook(); | |
this.counter--; | |
this.printCounter(); | |
} | |
private void printCounter() { | |
System.out.println("当前的计数是:" + this.counter); | |
} | |
} |
具体分类
在实际使用过程中,为了可以单独调用装饰器类新增的方法,定义这种形式的装饰器模式称为半透明装饰器模式;而标准的装饰器模式是透明装饰器模式。
透明装饰器模式
在透明装饰器模式中,要求客户端完全针对抽象编程,并且可以在具体的装饰器类中将新增行为都定义为私有方法。
如下是推荐的代码示例:
// 使用抽象类型定义对象 | |
Component component = new ConcreteComponent(); |
如下是不推荐的代码示例:
// 使用具体类型定义对象 | |
ConcreteComponent component = new ConcreteComponent(); |
透明装饰器模式可以让客户端透明地使用装饰器之前的对象和装饰之后的对象,无需关心它们的区别,使用上没有任何差别。因为,对于我们而言,面向的总是最顶层的抽象类。
半透明装饰器模式
为了能够调用到新增方法,不得不用具体装饰器类型来定义装饰之后的对象,而被装饰的对象还是可以使用抽象类来定义。
其实,也就是因为使用到具体装饰器新增的方法,无法确定这个方法是否在其他同类装饰器上也存在,因此,也就无法嵌套使用装饰器了。
如下是半透明装饰器模式的代码示例:
// 无论是使用具体类型定义对象 | |
ConcreteComponent component = new ConcreteComponent(); | |
// 或者是使用抽象类型定义对象 | |
Component component = new ConcreteComponent(); | |
// 使用具体装饰器类型定义 | |
ConcreteDecorator decorator = new ConcreteDecorator(component); | |
// 再调用具体装饰器自定义的方法 | |
decorator.otherMethod(); |
半透明装饰器模式更加灵活,设计相对简单,使用起来也很方便。
但是其最大的缺点是不能实现对同一个对象的多次装饰,而且客户端需要有区别地地对待装饰之前的对象和装饰之后的对象。
总结
优点
装饰器模式的主要优点如下:
- 对于扩展一个对象的功能,装饰器模式比继承更加灵活,不会导致类的个数急剧增加
- 可以通过一种动态的方式来扩展一个对象的功能
- 可以对一个对象进行多次装饰
- 具体类和具体装饰器类可以独立变化,用户可以根据需要增加新的具体类和具体装饰器类,符合开闭原则
缺点
装饰器模式的主要缺点如下:
- 使用装饰器模式进行系统设计时将产生很多小对象,如 Java 的
IO
类库 - 装饰器模式比继承更加灵活,但是也比继承更加容易出错,排错也很困难
适用场景
装饰器模式的适用场景如下:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
- 类已经被定义为不可被继承,但是又需要对类进行扩展的情况
- 系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长
源码
在 JDK 中,提供的 IO
类库使用了装饰器模式。