设计模式 | 装饰者模式及典型应用

前言

本文的主要内容:java

  • 介绍装饰者模式nginx

  • 示例spring

  • 源码分析装饰者模式的典型应用apache

    • Java I/O 中的装饰者模式编程

    • spring session 中的装饰者模式设计模式

    • Mybatis 缓存中的装饰者模式数组

  • 总结缓存

装饰者模式

装饰者模式(Decorator Pattern):动态地给一个对象增长一些额外的职责,增长对象功能来讲,装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。微信

在装饰者模式中,为了让系统具备更好的灵活性和可扩展性,咱们一般会定义一个抽象装饰类,而将具体的装饰类做为它的子类网络

角色

Component(抽象构件):它是具体构件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,它的引入可使客户端以一致的方式处理未被装饰的对象以及装饰以后的对象,实现客户端的透明操做。

ConcreteComponent(具体构件):它是抽象构件类的子类,用于定义具体的构件对象,实现了在抽象构件中声明的方法,装饰器能够给它增长额外的职责(方法)。

Decorator(抽象装饰类):它也是抽象构件类的子类,用于给具体构件增长职责,可是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用,经过该引用能够调用装饰以前构件对象的方法,并经过其子类扩展该方法,以达到装饰的目的。

ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向构件添加新的职责。每个具体装饰类都定义了一些新的行为,它能够调用在抽象装饰类中定义的方法,并能够增长新的方法用以扩充对象的行为。

因为具体构件类和装饰类都实现了相同的抽象构件接口,所以装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任,换言之,客户端并不会以为对象在装饰前和装饰后有什么不一样。装饰模式能够在不须要创造更多子类的状况下,将对象的功能加以扩展。

装饰模式的核心在于抽象装饰类的设计

示例

煎饼抽象类

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 I/O的做用。

Java I/O的做用图

由上图可知在Java中应用程序经过输入流(InputStream)的Read方法从源地址处读取字节,而后经过输出流(OutputStream)的Write方法将流写入到目的地址。

流的来源主要有三种:本地的文件(File)、控制台、经过socket实现的网络通讯

下面的图能够看出Java中的装饰者类和被装饰者类以及它们之间的关系,这里只列出了InputStream中的关系:

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的部分类图,更多细节请查看其它资料

OutputStream类图
Reader类图
Writer类图

spring cache 中的装饰者模式

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> 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 cache中类图关系

该类的主要功能:经过 Spring 的 TransactionSynchronizationManager 将其 put/evict/clear 操做与 Spring 管理的事务同步,仅在成功的事务的 after-commit 阶段执行实际的缓存 put/evict/clear 操做。若是没有事务是 active 的,将当即执行 put/evict/clear 操做

spring session 中的装饰者模式

注意:适配器模式的结尾也多是 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 的一个内部类 SessionRepositoryRequestWrapperServletRequestWrapper 的关系

ServletRequest类图

可见 ServletRequestWrapper 是第一层包装,HttpServletRequestWrapper 经过继承进行包装,增长了 HTTP 相关的功能,SessionRepositoryRequestWrapper 又经过继承进行包装,增长了 Session 相关的功能

Mybatis 缓存中的装饰者模式

org.apache.ibatis.cache 包的文件结构以下所示

Mybatis cache 中的装饰者模式

咱们经过类所在的包名便可判断出该类的角色,Cache 为抽象构件类,PerpetualCache 为具体构件类,decorators 包下的类为装饰类,没有抽象装饰类

经过名称也能够判断出装饰类所要装饰的功能

装饰者模式总结

装饰模式的主要优势以下:

  1. 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会致使类的个数急剧增长。

  2. 能够经过一种动态的方式来扩展一个对象的功能,经过配置文件能够在运行时选择不一样的具体装饰类,从而实现不一样的行为。

  3. 能够对一个对象进行屡次装饰,经过使用不一样的具体装饰类以及这些装饰类的排列组合,能够创造出不少不一样行为的组合,获得功能更为强大的对象。

  4. 具体构件类与具体装饰类能够独立变化,用户能够根据须要增长新的具体构件类和具体装饰类,原有类库代码无须改变,符合 “开闭原则”。

装饰模式的主要缺点以下:

  1. 使用装饰模式进行系统设计时将产生不少小对象,这些对象的区别在于它们之间相互链接的方式有所不一样,而不是它们的类或者属性值有所不一样,大量小对象的产生势必会占用更多的系统资源,在必定程序上影响程序的性能。

  2. 装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于屡次装饰的对象,调试时寻找错误可能须要逐级排查,较为繁琐。

适用场景

  1. 在不影响其余对象的状况下,以动态、透明的方式给单个对象添加职责。

  2. 当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时可使用装饰模式。不能采用继承的状况主要有两类:第一类是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增加;第二类是由于类已定义为不能被继承(如Java语言中的final类)。

参考:  
刘伟:设计模式Java版  
慕课网java设计模式精讲 Debug 方式+内存分析    
Java日志框架:slf4j做用及其实现原理  
HankingHu:由装饰者模式来深刻理解Java I/O总体框架  
HryReal:Java的io类的使用场景

推荐阅读

设计模式 | 简单工厂模式及典型应用  
设计模式 | 工厂方法模式及典型应用    
设计模式 | 抽象工厂模式及典型应用    
设计模式 | 建造者模式及典型应用  
设计模式 | 原型模式及典型应用    
设计模式 | 外观模式及典型应用

点击[阅读原文]可访问个人我的博客:http://laijianfeng.org

关注【小旋锋】微信公众号


本文分享自微信公众号 - 小旋锋(whirlysBigData)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索