装饰者(Decorator)
在不改变原有对象的基础之上,将功能附加到对象上。
提供了比继承更有弹性的替代方案(扩展原有对象功能)。
适用场景:
- 扩展一个类的功能或给一个类添加附加职责。
- 动态的给一个对象添加功能,这些功能可以再动态的撤销。
优缺点
优点:继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能;通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果;符合开闭原则。
缺点:会出现更多的代码,更多的类,增加程序复杂性;动态装饰时、多层装饰时会更复杂。
应用场景
我们考虑一个买煎饼的例子,人们可以自由地选择是否要在煎饼上加鸡蛋或者火腿,每次要加多少个,而总共价格是多少。
煎饼抽象类:
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
煎饼类:
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎饼";
}
@Override
protected int cost() {
return 8;
}
}
抽象装饰类(并不是真正的抽象类,因为这个场景中不需要抽象方法),这个类将抽象煎饼类作为成员属性,并且也继承了抽象煎饼类:
public class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
加鸡蛋的装饰类,继承了抽象装饰类:
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一个鸡蛋";
}
@Override
protected int cost() {
return super.cost()+1;
}
}
加火腿的装饰类,继承了抽象装饰类:
public class SausageDecorator extends AbstractDecorator {
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一根香肠";
}
@Override
protected int cost() {
return super.cost()+2;
}
}
客户端类:
public class Test {
public static void main(String[] args) {
ABattercake aBattercake;
aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + " 销售价格:" + aBattercake.cost());
}
}
输出:
煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠 销售价格:12
装饰类和具体组件类都继承了抽象组件类。所谓装饰,就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能,装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。
Java I/O中的应用
在Java中应用程序通过输入流(InputStream)的Read方法从源地址处读取字节,然后通过输出流(OutputStream)的Write方法将流写入到目的地址。
流的来源主要有三种:本地的文件(File)、控制台、通过socket实现的网络通信。
下面查看其中InputStream的类图,而关于OutputStream、Reader、Writer等都与此类似:
由上图可以看出只要继承了FilterInputStream的类就是装饰者类,可以用于包装其他的流,装饰者类还可以对装饰者和类进行再包装。以下是对其中部分类的简要介绍:
流名称 | 简介 |
---|---|
ByteArrayInputStream | 字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中 |
PipedInputStream | 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯 |
FileInputStream | 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作 |
PushBackInputStream | 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中 |
BufferedInputStream | 带缓冲的输入流一次读很多字节先放到内存中,等缓冲区满的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,因此效率很高 |
DataInputStream | 允许应用程序以与机器无关方式从底层输入流中读取基本Java数据类型 |
Spring中的应用
查看org.springframework.cache.transaction
下的TransactionAwareCacheDecorator
:
该类实现了Cache
接口,同时将Cache
组合到类中成为了成员属性,所以可以大胆猜测TransactionAwareCacheDecorator
是一个装饰类,不过这里并没有抽象装饰类,且TransactionAwareCacheDecorator
没有子类,这里的装饰类关系并没有Java I/O中的装饰关系那么复杂。
实际上,Spring cache是对缓存使用的抽象,通过它我们可以在不侵入业务代码的基础上让现有代码即刻支持缓存。通过Spring的TransactionSynchronizationManager
将其缓存操作与Spring管理的事务同步,仅在成功事务的提交之后执行实际的缓存操作。
MyBatis中的应用
查看包org.apache.ibatis.cache
:
参考资料
- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
- 慕课网java设计模式精讲 Debug 方式+内存分析
- CS-NOTE 设计模式
- 装饰者模式及典型应用