• 源码中的设计模式--装饰器模式


    一、模式入场

    有一句很经典的小品台词是“换个马甲我就不认识你了吗”,哈哈,这个比方正好用在今天要分享的装饰器模式上。先看下《head first 设计模式》中给的释义。

    装饰者模式  动态地将责任附加到对象上。若要扩展功能,装饰者提供了比基层更有弹性的替代方案。

    细心的小伙伴发现了这个释义怎么是装饰者模式,今天说的不是装饰器模式吗,其实这两个名称所代表的意思是一样的,为了保持和书上一致这里是装饰者模式,后续统一称为装饰器。

    这个释义太抽象太理论了,下面通俗的讲下。说到“装饰”二字,肯定第一时间想到的就是要有装饰者和被装饰者,正如前面说到的“换个马甲我就不认识你了吗”,这里的马甲可以理解为装饰者,穿马甲的就是被装饰者,放到装饰器模式里稍微有些不同,我们继续往下说。“装饰”,可以简单的理解为“伪装”,可以伪装成另外一个样子,也可以伪装成某种不同于原物的一种行为,所以在装饰者和被装饰者之间肯定存在某种相似,才可以使用装饰物去装饰被装饰者。用在java的设计模式中,我们讲的更多的是行为,也就是一个类所能完成的操作是可以用来装饰的。

    下面简单的根据一个UML图来了解下装饰器模式,

     

    上图中Component是一个接口,有两个方法methodA、methodB,有三个实现类ComponentA、ComponentB、ComponentDecoratorA,可以看到ComponentDecoratorA和其他两个实现类是不一样的,它有一个Component的属性,其他的从UML中看不出其他,当然在methodA、methodB方法中别有洞天,后面会说到。这里的CompoentDecoratorA其实就是一个装饰器类,任何实现了Component接口的类,都可以被它装饰,完成相应的功能。

    可以看到装饰器模式中有实现(继承),还有组合。

    二、深入装饰器模式

    上面对装饰器模式已经大体有了一个了解,下面通过一个具体的例子来实现一个简单的装饰器模式。

    Component.java

    复制代码
    package com.example.decorator;
    
    public interface Component {
        String methodA(String params);
        void methodB();
    }
    复制代码

    ComponentA.java

    复制代码
    package com.example.decorator;
    
    public class ComponentA implements Component{
        //返回字符串
        @Override
        public String methodA(String params) {
            return "ComponentA methodA";
        }
        //打印字符串
        @Override
        public void methodB() {
            System.out.println("ComponentA methodB");
        }
    }
    复制代码

    ComponentB.java

    复制代码
    package com.example.decorator;
    
    public class ComponentB implements Component{
        //返回字符串
        @Override
        public String methodA(String params) {
            return "ComponentB methodA";
        }
       //打印字符串
        @Override
        public void methodB() {
            System.out.println("ComponentB methodB");
        }
    }
    复制代码

    ComponentDecoratorA.java

    复制代码
    package com.example.decorator;
    
    public class ComponentDecoratorA implements Component{
        //Component的实例
        private Component component;
       //构造函数
        public ComponentDecoratorA(Component component){
            this.component=component;
        }
       //调用component的methodA方法,返回字符串
        @Override
        public String methodA(String params) {
            String decoratorStr=component.methodA(params);
    
            return "ComponentDecoratorA methodA,"+decoratorStr;
        }
       //调用component的methodB方法,打印字符串
        @Override
        public void methodB() {
            component.methodB();
            System.out.println("ComponentDecoratorA methodB");
        }
    }
    复制代码

    下面看测试代码,

    复制代码
    package com.example.decorator;
    
    public class TestDecorator {
        public static void main(String[] args) {
           //一个Component实例,被包装的实例
            Component component=new ComponentA();
           //使用ComponentDecoratorA进行包装
            Component component1=new ComponentDecoratorA(component);
            String str=component1.methodA("");
            System.out.println(str);
        }
    }
    复制代码

    看下测试结果,

    ComponentDecoratorA methodA,ComponentA methodA
    
    Process finished with exit code 0

    符合测试预期。

    很简单吧,这就是装饰器模式,总结以下要点,

    1、装饰者和被装饰者要实现统一的接口;

    2、在装饰者对象中持有被装饰者的对象实例;

    3、在装饰者行为中,主动调用被装饰者行为;

    很多小伙伴会问,装饰者和被装饰者必须实现统一的接口(interface)吗,使用抽象类可以吗,其实是可以的,上述的接口可以理解为接口和抽象类,我们说只要他们有共同的行为即可,不必太拘泥于定义。

    另外,在装饰器模式中,运用了实现(继承)和组合设计原则

    三、追寻源码

    上面我们已经学会了使用装饰器模式,让我们继续在源码中找寻它的影子,学习下优秀的人是怎么使用装饰器模式的,让我们的代码越来越好。

    1、java文件系统

    在Java实现的API中已经有了装饰器模式的使用,而且在日常开发中很常用,不知道你注意到没有,如果没有下次在使用文件操作类的时候可以留心下哦。

    在java的文件系统中,有字节流和字符流,又分为输入和输出,分别是InputStream、OutputStream、Reader、Writer。以InputStream来举例吧,在inputStream下有一个FilterInputStream,这是一个抽象类,该类便是一个装饰者类的接口,装饰所有实现了InputStream的类,

    另外,这里的InputStream是抽象类,

    看下其重要的read方法,在装饰者FilterInputStream中是怎么实现的,

    可以看到调用的是具体被装饰者的read方法,由于FilterInputStream是抽象的,我们看下其具体的一个实现类也就是具体的一个装饰者的实现,看下BufferedInputStream,

    复制代码
    public
    class BufferedInputStream extends FilterInputStream {
       //默认的缓冲大小
        private static int DEFAULT_BUFFER_SIZE = 8192;
       //最大
        private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
       //缓存区
        protected volatile byte buf[];
    
        protected int count;
    
        protected int pos;
        
        protected int markpos = -1;
    
        protected int marklimit;
    
        /**
         * Creates a <code>BufferedInputStream</code>
         * and saves its  argument, the input stream
         * <code>in</code>, for later use. An internal
         * buffer array is created and  stored in <code>buf</code>.
         *
         * @param   in   the underlying input stream.
         */
        public BufferedInputStream(InputStream in) {
            this(in, DEFAULT_BUFFER_SIZE);
        }
    复制代码

    该类的代码有删改,可以看到BufferedInputStream中定义了很多属性,这些数据都是为了可缓冲读取来作准备的,看到其有构造方法会传入一个InputStream的实例。实际编码如下

    //被装饰的对象,文件输入流
    InputStream in=new FileInputStream("/root/doc/123.txt");
    //装饰对象,可缓冲
    InputStream bufferedIn=new BufferedInputStream(in);
    bufferedIn.read();

    上面的代码便使用的装饰器模式进行的可缓冲的文件读取,代码很眼熟吧,其实你已经使用了装饰器模式。

    上面仅是拿InputStream进行了举例说明其实,在java的IO系统中,FilterInputStream、FilterOutputStream、FilterReader、FilterWriter抽象类都是装饰器模式的体现,其抽象类的子类都是装饰者类。

    2、mybatis缓存系统

    mybatis自带一级缓存,其缓存设计就是使用的装饰器模式,我们先来看下其cache接口

    上图红框中标出的是Cache接口的直接实现PerpetualCache,这个类可以作为被装饰者,再看其他的实现均在org.apache.ibatis.cache.decorators包中,那么也就是装饰者,看下LruCache的实现,仅贴出部分代码,

    复制代码
    public class LruCache implements Cache {
        //Cache实例
        private final Cache delegate;
       //实现LRU算法的辅助map
        private Map<Object, Object> keyMap;
        private Object eldestKey;
       //构造函数,传入一个Cache,用来初始胡delegate和其他参数
        public LruCache(Cache delegate) {
            this.delegate = delegate;
            this.setSize(1024);
        }
    }
    复制代码

    这个代码和最开始演示的Component的那个例子很像,至于LRU缓存怎么实现的,各位小伙伴可以自行了解。下次再使用到mybatis的缓存,你就可以自豪的说这是装饰器模式。

    3、mybatis的Executor执行器

    在mybatis中真正负责执行sql语句的是Executor接口,

    该接口有以下几个实现类:CachingExecutor、BaseExecutor、SimpleExecutor等,重点看下CachingExecutor、SimpleExecutor

    CachingExecutor应该是装饰者,看下SimpleExecutor

    这个应该是被装饰者,它在执行具体的操作。

    四、总结

    本文分享了装饰器模式及在源码中的使用,需要几种以下几点,

    1、装饰者和被装饰者要实现统一的接口;

    2、在装饰者对象中持有被装饰者的对象实例;

    3、在装饰者行为中,主动调用被装饰者行为;

    装饰器模式很好的体现了继承(实现)和组合的设计原则。

     

  • 相关阅读:
    BOM介绍以及应用以及this指向问题
    中文编程软件视频推荐,自学编程电脑推荐,中文编程开发语言工具下载
    长视频又添新变数
    (附源码)springboot青少年公共卫生教育平台 毕业设计 643214
    You have an error in your SQL syntax; check the manual that corresponds to...
    矩阵分析学习笔记(六):有理标准型和Jordan标准型、复数域上矩阵的特征结构
    ElasticSearch 学习7 集成ik分词器
    APK漏洞扫描工具
    C++中memset函数与min_element(max_element)
    ifconfig与ip命令的比较
  • 原文地址:https://www.cnblogs.com/teach/p/16214217.html