zoukankan      html  css  js  c++  java
  • 【设计模式】【结构型模式】装饰器模式

    概念

    定义

    装饰模式指动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。

    这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

    这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

    一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。在不想增加很多子类的情况下扩展类时可以使用装饰器模式。

    对象增强的常用方式

    很多时候我们可能对Java提供给我们的对象不满意,不能满足我们的功能。此时我们就想对Java原对象进行增强,能够实现我们想要的功能就好~

    一般来说,实现对象增强有三种方式:

    1、继承:继承父类,子类扩展
    2、装饰器模式:使用“包装”的方式来增强对象
    3、代理模式:静态代理和动态代理

    继承

    最简单的方式就是继承父类,子类扩展来达到目的。虽然简单,但是这种方式的缺陷非常大:

    1、如果父类是带有数据、信息、属性的话,那么子类无法增强。
    2、子类实现了之后需求无法变更,增强的内容是固定的。

    第一个缺点就拿以前在学JDBC的时候来说:

    开始想要自己写一个简易的JDBC连接池,连接池由List来管理。显然我们的对象是Connection,当写到close()方法的时候卡住了。

    因为我们想要的功能是:调用close()是Connection返回到“连接池”(集合)中,而不是关闭掉。

    此时我们不能使用继承父类的方式来实现增强。因为Connection对象是由数据库厂商来实现的,在得到Connection对象的时候绑定了各种信息(数据库的username、password、具体的数据库是啥等等)。我们子类继承Connection是无法得到对应的数据的!就更别说调用close()方法了。

    第二点我也举个例子:

    现在我设计一个电话类:

    public class Phone {
        // 可以打电话
        public void call() {
            System.out.println("打电话给周围的人关注公众号Java3y");
        }
    }
    

    此时,我想打电话之前能听彩铃,于是我继承Phone类,实现我想要的功能。

    public class MusicPhone extends Phone {
    
        // 听彩铃
        public void listenMusic() {
            System.out.println("我怀念的是无话不说,我怀念的是一起做梦~~~~~~");
        }
    
        @Override
        public void call() {
    
            // 在打电话之前听彩铃
            listenMusic();
    
            super.call();
        }
    }
    

    我们的功能就做好了。此时,我又突然想实现多一个需求了,我想要听完电话之后告诉我一下当前的时间是多少。没事,我们又继承来增强一下:

    // 这里继承的是MusicPhone类
    public class GiveCurrentTimePhone extends MusicPhone {
    
        // 给出当前的时间
        public void currentTime() {
            System.out.println("当前的时间是:" + System.currentTimeMillis());
        }
    
        @Override
        public void call() {
            super.call();
    
            // 打完电话提示现在的时间是多少啦
            currentTime();
        }
    }
    

    所以我们还是可以完成这种需求的。

    可是我需求现在又想变了:

    我不想听彩铃了,只想听完电话通知一下时间就好了(可是我们的通知时间电话类是继承在听彩铃的电话类基础之上的)。

    我又有可能:我想在听电话之前报告一下时间,听完电话听音乐。

    如果需求变动很大的情况下,而我们又用继承的方式来实现这样会导致一种现象:类爆炸(类数量激增)!并且继承的层次可能会比较多。

    所以,我们可以看到子类继承父类这种方式来扩展是十分局限的,不灵活。

    因此就引出了装饰模式

    装饰模式实例

    首先我们来看看装饰模式是怎么用的吧。

    示例

    1、定义Component接口Phone和具体实现IphoneX

    电话接口:
    
    // 一个良好的设计是抽取成接口或者抽象类的
    public interface Phone {
    
        // 可以打电话
        void call();
    }
    
    具体的实现:
    
    public class IphoneX implements Phone {
    
    
        @Override
        public void call() {
            System.out.println("打电话给周围的人关注公众号Java3y");
        }
    }
    

    2、定义装饰器 PhoneDecorate,它实现了接口Phone,以组合的方式接收具体实现类IphoneX。

    // 装饰器,实现接口
    public abstract class PhoneDecorate implements Phone {
    
        // 以组合的方式来获取默认实现类
        private Phone phone;
        public PhoneDecorate(Phone phone) {
            this.phone = phone;
        }
    
        @Override
        public void call() {
            phone.call();
        }
    }
    

    3、有了装饰器以后,就可以通过继承装饰器来进行功能扩展了。

    定义需要扩展的装饰器。

    我们想要在打电话之前听音乐:

    // 继承着装饰器PhoneDecorate来扩展
    public class MusicPhone extends PhoneDecorate {
    
        public MusicPhone(Phone phone) {
            super(phone);
        }
    
        // 定义想要扩展的功能
        public void listenMusic() {
    
            System.out.println("继续跑 带着赤子的骄傲,生命的闪耀不坚持到底怎能看到,与其苟延残喘不如纵情燃烧");
    
        }
    
        // 重写打电话的方法
        @Override
        public void call() {
    
            // 在打电话之前听音乐
            listenMusic();
            super.call();
        }
    }
    

    现在我也想在打完电话后通知当前的时间,于是我们也继承装饰类来扩展:

    // 这里继承的是装饰器类PhoneDecorate
    public class GiveCurrentTimePhone extends PhoneDecorate  {
    
    
        public GiveCurrentTimePhone(Phone phone) {
            super(phone);
        }
    
        // 自定义想要实现的功能:给出当前的时间
        public void currentTime() {
            System.out.println("当前的时间是:" + System.currentTimeMillis());
        }
    
        // 重写要增强的方法
        @Override
        public void call() {
            super.call();
            // 打完电话后通知一下当前时间
            currentTime();
        }
    }
    

    需求变更

    就目前这样看起来,比我直接继承父类要麻烦,而功能效果是一样的。那么装饰模式的优势在哪呢?

    如果此时,我不想在打电话之前听到彩铃了,很简单:我们不装饰它就好了!

    此时,我想在打电话前报告一下时间,在打完电话之后听彩铃。

    注意:
    虽然说要改动类中的代码,但是这种改动是合理的。 因为我定义出的GiveCurrentTimePhone类和MusicPhone类本身从语义上就没有规定扩展功能的执行顺序。

    而继承不一样:先继承Phone->实现MusicPhone->再继承MusicPhone实现GiveCurrentTimePhone。这是固定的,从继承的逻辑上已经写死了具体的代码,是难以改变的。

    所以我们还是可以很简单地完成功能:

    示例解说

    可能有的同学在看完上面的代码之后,还是迷迷糊糊地不知道装饰模式是怎么实现“装饰”的。下面我就再来解析一下:

    第一步:我们有一个Phone接口,该接口定义了Phone的功能

    第二步:我们有一个最简单的实现类iPhoneX

    第三步:写一个装饰器抽象类PhoneDecorate,以组合(构造函数传递)的方式接收我们最简单的实现类iPhoneX。其实装饰器抽象类的作用就是代理(核心的功能还是由最简单的实现类iPhoneX来做,只不过在扩展的时候可以添加一些没有的功能而已)。

    第四步:想要扩展什么功能,就继承PhoneDecorate装饰器抽象类,将想要增强的对象(最简单的实现类iPhoneX或者已经被增强过的对象)传进去,完成我们的扩展!
    再来看看下面的图,就懂了!

    往往我们的代码可以省略起来,成了这个样子(是不是和IO的非常像!)

    // 先增强听音乐的功能,再增强通知时间的功能
    Phone phone = new GiveCurrentTimePhone(new MusicPhone(new IphoneX()));
    

    结果是一样的:

    应用

    Java中的IO

    JavaI/O库的对象结构图如下:

    抽象被装饰者(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

    具体被装饰者(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

    抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

    具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

    抽象被装饰者InputStream

    具体被装饰者FileInputStream

    public class FileInputStream extends InputStream
    {}
    

    抽象装饰者类FilterInputStream

    具体装饰角色PushbackInputStream

    使用I/O流读取文件内容的简单操作示例。

    FileInputStream对象相当于原始的被装饰的对象, 而BufferedInputStream对象和DataInputStream对象则相当于装饰器。

    使用Decorator设计模式增强request对象

    有一种情况下,必须使用Decorator设计模式:即被增强的对象,开发人员只能得到它的对象,无法得到它的class文件。

    比如request、response对象,开发人员之所以在servlet中能通过sun公司定义的HttpServletRequest esponse接口去操作这些对象,是因为Tomcat服务器厂商编写了request、response接口的实现类。web服务器在调用servlet时,会用这些接口的实现类创建出对象,然后传递给servlet程序。

    此种情况下,由于开发人员根本不知道服务器厂商编写的request、response接口的实现类是哪个?在程序中只能拿到服务器厂商提供的对象,因此就只能采用Decorator设计模式对这些对象进行增强。

    Decorator设计模式的实现

    1、首先看需要被增强对象继承了什么接口或父类,编写一个类也去继承这些接口或父类。
    2、在类中定义一个变量,变量类型即需增强对象的类型。
    3、在类中定义一个构造函数,接收需增强的对象。
    4、覆盖需增强的方法,编写增强的代码。

    使用Decorator设计模式增强request对象

    Servlet API 中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,HttpServletRequestWrapper 类实现了request 接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request 对象的对应方法,以避免用户在对request对象进行增强时需要实现request接口中的所有方法。

    编写一个用于处理中文乱码的过滤器CharacterEncodingFilter,代码如下:

    package me.gacl.web.filter;
    
    import java.io.IOException;
    
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    
    /**
    * @ClassName: CharacterEncodingFilter
    * @Description: 此过滤器用来解决解决get、post请求方式下的中文乱码问题
    * @author: 孤傲苍狼
    * @date: 2014-8-31 下午11:09:37
    *
    */ 
    public class CharacterEncodingFilter implements Filter {
    
        private FilterConfig filterConfig = null;
        //设置默认的字符编码
        private String defaultCharset = "UTF-8";
    
        public void doFilter(ServletRequest req, ServletResponse resp,
                FilterChain chain) throws IOException, ServletException {
            
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
            //得到在web.xml中配置的字符编码
            String charset = filterConfig.getInitParameter("charset");
            if(charset==null){
                charset = defaultCharset;
            }
            request.setCharacterEncoding(charset);
            response.setCharacterEncoding(charset);
            response.setContentType("text/html;charset="+charset);
            
            MyCharacterEncodingRequest requestWrapper = new MyCharacterEncodingRequest(request);
            chain.doFilter(requestWrapper, response);
        }
    
        public void init(FilterConfig filterConfig) throws ServletException {
            //得到过滤器的初始化配置信息
            this.filterConfig = filterConfig;
        }
        
        public void destroy() {
    
        }
    }
    
    /**
    * @ClassName: MyCharacterEncodingRequest
    * @Description: Servlet API中提供了一个request对象的Decorator设计模式的默认实现类HttpServletRequestWrapper,
    * (HttpServletRequestWrapper类实现了request接口中的所有方法,但这些方法的内部实现都是仅仅调用了一下所包装的的 request对象的对应方法)
    * 以避免用户在对request对象进行增强时需要实现request接口中的所有方法。
    * 所以当需要增强request对象时,只需要写一个类继承HttpServletRequestWrapper类,然后在重写需要增强的方法即可
    * @author: 孤傲苍狼
    * @date: 2014-9-2 下午10:42:57
    *     1.实现与被增强对象相同的接口 
        2、定义一个变量记住被增强对象
        3、定义一个构造函数,接收被增强对象
        4、覆盖需要增强的方法
        5、对于不想增强的方法,直接调用被增强对象(目标对象)的方法
    */ 
    class MyCharacterEncodingRequest extends HttpServletRequestWrapper{
        //定义一个变量记住被增强对象(request对象是需要被增强的对象)
        private HttpServletRequest request;
        //定义一个构造函数,接收被增强对象
        public MyCharacterEncodingRequest(HttpServletRequest request) {
            super(request);
            this.request = request;
        }
        /* 覆盖需要增强的getParameter方法
         * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
         */
        @Override
        public String getParameter(String name) {
            try{
                //获取参数的值
                String value= this.request.getParameter(name);
                if(value==null){
                    return null;
                }
                //如果不是以get方式提交数据的,就直接返回获取到的值
                if(!this.request.getMethod().equalsIgnoreCase("get")) {
                    return value;
                }else{
                    //如果是以get方式提交数据的,就对获取到的值进行转码处理
                    value = new String(value.getBytes("ISO8859-1"),this.request.getCharacterEncoding());
                    return value;
                }
            }catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    优缺点

    优点:
    1、装饰类和被装饰类可以独立发展,不会相互耦合,互相都不用知道对方的存在
    2、装饰模式是继承的一个替代模式,无论包装多少层,返回的对象都是is-a的关系(上面的例子:包装完还是Phone类型)。对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
    3、装饰模式可以动态扩展一个实现类的功能,只要继承了装饰器就可以动态扩展想要的功能了。
    4、可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
    5、用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。

    缺点:
    1、多层装饰比较复杂,提高了系统的复杂度,不利于调试。
    2、使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
    3、使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能。

    总结

    对象增强的三种方式:

    • 继承
    • 装饰器模式
    • 代理模式

    那么只要遇到Java提供给我们的API不够用,增强一下就行了。在写代码时,某个类被写死了,功能不够用,增强一下就可以了!本文介绍了继承和装饰模式进行增强的方式,代理模式后续专门进行讲解。

    装饰器模式适用于在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

    特点
    1、装饰者和被装饰者有相同的接口(或有相同的父类)。
    2、装饰者保存了一个被装饰者的引用。
    3、在运行时动态地为对象添加属性,不必改变对象的结构。

    参考资料:
    包装模式就是这么简单啦
    装饰器模式
    JAVA设计模式初探之装饰者模式
    javaweb学习总结(四十三)——Filter高级开发

  • 相关阅读:
    go函数
    Linux 查看磁盘容量、查找大文件、查找大目录
    五分钟理解一致性哈希算法(consistent hashing)
    使用Java实现三个线程交替打印0-74
    Python实现IOC控制反转
    Wannafly挑战赛5 A珂朵莉与宇宙 前缀和+枚举平方数
    Yandex Big Data Essentials Week1 Scaling Distributed File System
    Yandex Big Data Essentials Week1 Unix Command Line Interface Processes managing
    Yandex Big Data Essentials Week1 Unix Command Line Interface File Content exploration
    Yandex Big Data Essentials Week1 Unix Command Line Interface File System exploration
  • 原文地址:https://www.cnblogs.com/z00377750/p/9494110.html
Copyright © 2011-2022 走看看