本文的主要内容:html
装饰者模式(Decorator Pattern):动态地给一个对象增长一些额外的职责,增长对象功能来讲,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。java
在装饰者模式中,为了让系统具备更好的灵活性和可扩展性,咱们一般会定义一个抽象装饰类,而将具体的装饰类做为它的子类spring
Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可使客户端以一致的方式处理未被装饰的对象以及装饰以后的对象,实现客户端的透明操做。apache
ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器能够给它增长额外的职责(方法)。编程
Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增长职责,可是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,经过该引用能够调用装饰以前构件对象的方法,并经过其子类扩展该方法,以达到装饰的目的。设计模式
ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每个具体装饰类都定义了一些新的行为,它能够调用在抽象装饰类中定义的方法,并能够增长新的方法用以扩充对象的行为。数组
因为具体构件类和装饰类都实现了相同的抽象构件接口,所以装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会以为对象在装饰前和装饰后有什么不一样。装饰模式能够在不须要创造更多子类的状况下,将对象的功能加以扩展。缓存
装饰模式的核心在于抽象装饰类的设计。bash
煎饼抽象类微信
public abstract class ABattercake {
protected abstract String getDesc();
protected abstract int cost();
}
复制代码
煎饼类,继承了煎饼抽象类,一个煎饼 8 块钱
public class Battercake extends ABattercake {
@Override
protected String getDesc() {
return "煎饼";
}
@Override
protected int cost() {
return 8;
}
}
复制代码
抽象装饰类,须要注意的是,抽象装饰类经过成员属性的方式将 煎饼抽象类组合进来,同时也继承了煎饼抽象类,且这里定义了新的业务方法 doSomething()
public abstract class AbstractDecorator extends ABattercake {
private ABattercake aBattercake;
public AbstractDecorator(ABattercake aBattercake) {
this.aBattercake = aBattercake;
}
protected abstract void doSomething();
@Override
protected String getDesc() {
return this.aBattercake.getDesc();
}
@Override
protected int cost() {
return this.aBattercake.cost();
}
}
复制代码
鸡蛋装饰器,继承了抽象装饰类,鸡蛋装饰器在父类的基础上增长了一个鸡蛋,同时价格加上 1 块钱
public class EggDecorator extends AbstractDecorator {
public EggDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@Override
protected String getDesc() {
return super.getDesc() + " 加一个鸡蛋";
}
@Override
protected int cost() {
return super.cost() + 1;
}
public void egg() {
System.out.println("增长了一个鸡蛋");
}
}
复制代码
香肠装饰器,与鸡蛋装饰器相似,继承了抽象装饰类,给在父类的基础上加上一根香肠,同时价格增长 2 块钱
public class SausageDecorator extends AbstractDecorator{
public SausageDecorator(ABattercake aBattercake) {
super(aBattercake);
}
@Override
protected void doSomething() {
}
@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 = new Battercake();
System.out.println(aBattercake.getDesc() + ", 销售价格: " + aBattercake.cost());
}
}
复制代码
输出
煎饼, 销售价格: 8
复制代码
二、购买一个加鸡蛋的煎饼
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 销售价格: " + aBattercake.cost());
}
}
复制代码
输出
煎饼 加一个鸡蛋, 销售价格: 9
复制代码
三、购买一个加两个鸡蛋的煎饼
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 销售价格: " + aBattercake.cost());
}
}
复制代码
输出
煎饼 加一个鸡蛋 加一个鸡蛋, 销售价格: 10
复制代码
四、购买一个加两个鸡蛋和一根香肠的煎饼
public class Test {
public static void main(String[] args) {
ABattercake aBattercake = new Battercake();
aBattercake = new EggDecorator(aBattercake);
aBattercake = new EggDecorator(aBattercake);
aBattercake = new SausageDecorator(aBattercake);
System.out.println(aBattercake.getDesc() + ", 销售价格: " + aBattercake.cost());
}
}
复制代码
输出
煎饼 加一个鸡蛋 加一个鸡蛋 加一根香肠, 销售价格: 12
复制代码
画出UML类图以下所示
因为具体构件类和装饰类都实现了相同的抽象构件接口,所以装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会以为对象在装饰前和装饰后有什么不一样。
譬如咱们给煎饼加上一个鸡蛋能够这么写 aBattercake = new EggDecorator(aBattercake);
,客户端仍然能够把 aBattercake
当成原来的 aBattercake
同样,不过如今的 aBattercake
已经被装饰加上了鸡蛋
装饰模式能够在不须要创造更多子类的状况下,将对象的功能加以扩展。
在上面的示例中,装饰后的对象是经过抽象构建类类型 ABattercake
的变量来引用的,在鸡蛋装饰器这个类中咱们新增了 egg()
方法,若是此时咱们想要单独调用该方法是调用不到的
除非引用变量的类型改成 EggDecorator
,这样就能够调用了
EggDecorator eggBattercake = new EggDecorator(aBattercake);
eggBattercake.egg();
复制代码
在实际使用过程当中,因为新增行为可能须要单独调用,所以这种形式的装饰模式也常常出现,这种装饰模式被称为半透明(Semi-transparent)装饰模式,而标准的装饰模式是透明(Transparent)装饰模式。
(1) 透明装饰模式
在透明装饰模式中,要求客户端彻底针对抽象编程,装饰模式的透明性要求客户端程序不该该将对象声明为具体构件类型或具体装饰类型,而应该所有声明为抽象构件类型。
(2) 半透明装饰模式
透明装饰模式的设计难度较大,并且有时咱们须要单独调用新增的业务方法。为了可以调用到新增方法,咱们不得不用具体装饰类型来定义装饰以后的对象,而具体构件类型仍是可使用抽象构件类型来定义,这种装饰模式即为半透明装饰模式。
半透明装饰模式能够给系统带来更多的灵活性,设计相对简单,使用起来也很是方便;可是其最大的缺点在于不能实现对同一个对象的屡次装饰,并且客户端须要有区别地对待装饰以前的对象和装饰以后的对象。
(1) 尽可能保持装饰类的接口与被装饰类的接口相同,这样,对于客户端而言,不管是装饰以前的对象仍是装饰以后的对象均可以一致对待。这也就是说,在可能的状况下,咱们应该尽可能使用透明装饰模式。
(2) 尽可能保持具体构件类是一个“轻”类,也就是说不要把太多的行为放在具体构件类中,咱们能够经过装饰类对其进行扩展。
(3) 若是只有一个具体构件类,那么抽象装饰类能够做为该具体构件类的直接子类。
使用 Java I/O 的时候老是有各类输入流、输出流、字符流、字节流、过滤流、缓冲流等等各类各样的流,不熟悉里边的设计模式的话总会看得云里雾里的,如今经过设计模式的角度来看 Java I/O,会好理解不少。
先用一幅图来看看Java I/O究竟是什么,下面的这幅图生动的刻画了Java I/O的做用。
由上图可知在Java中应用程序经过输入流(InputStream)的Read方法从源地址处读取字节,而后经过输出流(OutputStream)的Write方法将流写入到目的地址。
流的来源主要有三种:本地的文件(File)、控制台、经过socket实现的网络通讯
下面的图能够看出Java中的装饰者类和被装饰者类以及它们之间的关系,这里只列出了InputStream中的关系:
由上图能够看出只要继承了FilterInputStream的类就是装饰者类,能够用于包装其余的流,装饰者类还能够对装饰者和类进行再包装。
这里总结几种经常使用流的应用场景:
流名称 | 应用场景 |
---|---|
ByteArrayInputStream | 访问数组,把内存中的一个缓冲区做为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上 |
StringBufferInputStream | 把一个 String 对象做为。InputStream。不建议使用,在转换字符的问题上有缺陷 |
FileInputStream | 访问文件,把一个文件做为 InputStream ,实现对文件的读取操做 |
PipedInputStream | 访问管道,主要在线程中使用,一个线程经过管道输出流发送数据,而另外一个线程经过管道输入流读取数据,这样可实现两个线程间的通信 |
SequenceInputStream | 把多个 InputStream 合并为一个 InputStream . “序列输入流”类容许应用程序把几个输入流连续地合并起来 |
DataInputStream | 特殊流,读各类基本类型数据,如byte、int、String的功能 |
ObjectInputStream | 对象流,读对象的功能 |
PushBackInputStream | 推回输入流,能够把读取进来的某些数据从新回退到输入流的缓冲区之中 |
BufferedInputStream | 缓冲流,增长了缓冲功能 |
下面看一下Java中包装流的实例:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class StreamDemo {
public static void main(String[] args) throws IOException{
DataInputStream in=new DataInputStream(new BufferedInputStream(new FileInputStream("D:\\hello.txt")));
while(in.available()!=0) {
System.out.print((char)in.readByte());
}
in.close();
}
}
复制代码
输出结果
hello world!
hello Java I/O!
复制代码
上面程序中对流进行了两次包装,先用 BufferedInputStream将FileInputStream包装成缓冲流也就是给FileInputStream增长缓冲功能,再DataInputStream进一步包装方便数据处理。
若是要实现一个本身的包装流,根据上面的类图,须要继承抽象装饰类 FilterInputStream
譬如来实现这样一个操做的装饰者类:将输入流中的全部小写字母变成大写字母
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
public class UpperCaseInputStream extends FilterInputStream {
protected UpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase(c));
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int result = super.read(b, off, len);
for (int i = off; i < off + result; i++) {
b[i] = (byte) Character.toUpperCase((char) b[i]);
}
return result;
}
public static void main(String[] args) throws IOException {
int c;
InputStream in = new UpperCaseInputStream(new FileInputStream("D:\\hello.txt"));
try {
while ((c = in.read()) >= 0) {
System.out.print((char) c);
}
} finally {
in.close();
}
}
}
复制代码
输出
HELLO WORLD!
HELLO JAVA I/O!
复制代码
整个Java IO体系都是基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)做为基类,下面画出OutputStream、Reader、Writer的部分类图,更多细节请查看其它资料
看 org.springframework.cache.transaction
包下的 TransactionAwareCacheDecorator
这个类
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public <T> T get(Object key, Class<T> type) {
return this.targetCache.get(key, type);
}
public void put(final Object key, final Object value) {
// 判断是否开启了事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 将操做注册到 afterCommit 阶段
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
} else {
this.targetCache.put(key, value);
}
}
// ...省略...
}
复制代码
该类实现了 Cache
接口,同时将 Cache
组合到类中成为了成员属性 targetCache
,因此能够大胆猜想 TransactionAwareCacheDecorator
是一个装饰类,不过这里并无抽象装饰类,且 TransactionAwareCacheDecorator
没有子类,这里的装饰类关系并无Java I/O 中的装饰关系那么复杂
该类的主要功能:经过 Spring 的 TransactionSynchronizationManager
将其 put/evict/clear
操做与 Spring 管理的事务同步,仅在成功的事务的 after-commit
阶段执行实际的缓存 put/evict/clear
操做。若是没有事务是 active
的,将当即执行 put/evict/clear
操做
注意:适配器模式的结尾也多是 Wrapper
类 ServletRequestWrapper
的代码以下:
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;
public ServletRequestWrapper(ServletRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request cannot be null");
}
this.request = request;
}
@Override
public Object getAttribute(String name) {
return this.request.getAttribute(name);
}
//...省略...
}
复制代码
能够看到该类对 ServletRequest
进行了包装,这里是一个装饰者模式,再看下图,spring session 中 SessionRepositoryFilter
的一个内部类 SessionRepositoryRequestWrapper
与 ServletRequestWrapper
的关系
可见 ServletRequestWrapper
是第一层包装,HttpServletRequestWrapper
经过继承进行包装,增长了 HTTP 相关的功能,SessionRepositoryRequestWrapper
又经过继承进行包装,增长了 Session 相关的功能
org.apache.ibatis.cache
包的文件结构以下所示
咱们经过类所在的包名便可判断出该类的角色,Cache
为抽象构件类,PerpetualCache
为具体构件类,decorators
包下的类为装饰类,没有抽象装饰类
经过名称也能够判断出装饰类所要装饰的功能
装饰模式的主要优势以下:
装饰模式的主要缺点以下:
适用场景:
参考:
刘伟:设计模式Java版
慕课网java设计模式精讲 Debug 方式+内存分析
Java日志框架:slf4j做用及其实现原理
HankingHu:由装饰者模式来深刻理解Java I/O总体框架
HryReal:Java的io类的使用场景
设计模式 | 简单工厂模式及典型应用
设计模式 | 工厂方法模式及典型应用
设计模式 | 抽象工厂模式及典型应用
设计模式 | 建造者模式及典型应用
设计模式 | 原型模式及典型应用
设计模式 | 外观模式及典型应用
更多内容可访问个人我的博客:laijianfeng.org