zoukankan      html  css  js  c++  java
  • 设计模式(三)_装饰器模式

    上篇学习了策略模式,现在回想下,什么是策略模式,好了。本篇主要介绍装饰器模式,just do it!

    什么是装饰器模式

    装饰器模式指的是动态的将责任附加到对象上。若要扩展功能,装饰器模式提供了比继承更弹性的替代方案。

    如何使用装饰器模式

    老王来到商场买衣服,需要买衣服,裤子,帽子......

    public class Wang {
    
        public void show(){
            System.out.println("我穿上衣服,累计花费100元");
            System.out.println("我穿上裤子,累计花费250元");
            System.out.println("我穿上帽子,花费300元");
        }
    }
    

    如果老王每新买一件衣服,都要修改一下这个show方法,这就不符合开闭原则。我们可以用装饰器模式。针对这个过程,我画了老王买衣服的uml图
    enter image description here

    观察上图,观察模式主要有4个角色

    • 抽象构件角色(给出一个抽象接口Person,以规范准备接受附加责任的对象)
    • 装饰器父类
    • 具体的装饰器对象
    • 被装饰的对象

    老王就是被装饰的对象,衣服帽子 就是装饰器
    从图中可以看出,装饰器和被装饰的2个特点,也是装饰器模式的关键

    • 装饰器和被装饰对象实现同一个接口;
    • 装饰器中使用了被装饰的对象

    代码实现
    (1) 抽象构件角色

    public interface Person {
    
        /**
         * 累积消费
         * @return
         */
        public double cost();
    
        public void show();
    }
    

    (2).老王,被装饰的对象

    
    public class Wang implements Person {
        @Override
        public double cost() {
            return 0;
        }
    
        @Override
        public void show() {
            System.out.println("我是赤裸裸的老王");
        }
    }
    

    (3).装饰器父类,和被装饰对象实现同一个接口Person

    public abstract class ClothesDecorator implements Person {
        protected  Person person;
    
        public ClothesDecorator(Person person){
            this.person = person;
        }
    }
    

    (4) 具体的装饰器类:衣服和帽子

    public class Jacket extends ClothesDecorator {
    
        public Jacket(Person person){
            super(person);
        }
        @Override
        public double cost() {
            return person.cost()+100;
        }
    
        @Override
        public void show() {
            person.show();
            System.out.println("买了夹克,累计花了"+this.cost());
        }
    }
    
    
    public class Hat extends ClothesDecorator{
        public Hat(Person person) {
            super(person);
        }
    
        @Override
        public double cost() {
            return person.cost()+50;
        }
    
        @Override
        public void show() {
            person.show();
            System.out.println("买了帽子,累计花了"+this.cost());
    
        }
    }
    

    测试

            Person wang = new Wang();
    
            wang  = new Jacket(wang);
    
            wang  = new Hat(wang);
            
    		//  wang  = new Hat(new Jacket(wang));
    		
            wang.show();
    
            System.out.println("买单:王总共消费"+wang.cost());
    

    输出结果

    我是赤裸裸的老王
    买了夹克,累计花了100.0
    买了帽子,累计花了150.0
    买单:王总共消费150.0
    

    如果还要买鞋子,只要动态创建鞋子的装饰类,就可以了,不用修改已经写好的类。也贯彻了开闭原则。

    使用装饰器模式的关键点

    • 装饰器和被装饰对象实现同一个接口,实际开发中也可能使用继承。
    • 装饰器中的方法可以调用被装饰对象提供的方法,以此实现功能累加的效果,例如,夹克装饰器和帽子装饰器中调用了 person.cost() + xx 实现累计消费金额的累加。

    实际案例

    装饰器模式在Java体系中的经典应用是Java I/O,下面讲解字节输入流InputStream
    我简单画了UMl图,并用颜色进行了标注

    enter image description here

    定义中说:“装饰器提供了比继承更有弹性的解决方案”,为甚么这样说?

    • InputStream假设这里写了两个实现类,FileInputStream,ObjectInputStream分别表示文件字节输入流,对象字节输入流
    • 现在我要给这两个输入流加入一点缓冲功能以提高输入流效率,使用继承的方式,那么就写一个BufferedInputStream,继承FileInputStream,ObjectInputStream,给它们加功能
    • 现在我有另外一个需求,需要给这两个输入流加入一点网络功能,那么就写一个SocketInputStream,继承继承FileInputStream,ObjectInputStream,给它们加功能。

    这样就导致2个问题
    1)、因为我要给哪个类加功能就必须继承它,比如我要给FileInputStream,ObjectInputStream加上缓冲功能、网络功能就得扩展出2*2=4个类,更多的以此类推,这样势必导致类数量不断膨胀
    2)、代码无法复用,给FileInputStream,ObjectInputStream加入缓冲功能,本身代码应该是一样的,现在却必须继承完毕后把一样的代码重写一遍,多此一举,代码修改的时候必须修改多个地方,可维护性很差

    所以,这个的时候我们就想到了一种解决方案:

    • 在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来.

    • 将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能
      这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生

    代码分析

    1、InputStream是一个抽象构件角色

    public abstract class InputStream implements Closeable {
    
        // MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to
        // use when skipping.
        private static final int MAX_SKIP_BUFFER_SIZE = 2048;
        ...
        }
    

    2、被装饰对象ByteArrayInputStream

    public
    class ByteArrayInputStream extends InputStream {
    
        /**
         * An array of bytes that was provided
         * by the creator of the stream. Elements <code>buf[0]</code>
         * through <code>buf[count-1]</code> are the
         * only bytes that can ever be read from the
         * stream;  element <code>buf[pos]</code> is
         * the next byte to be read.
         */
        protected byte buf[];
    
    	...
    	}
    

    3.装饰器父类FilterInputStream

    public
    class FilterInputStream extends InputStream {
        /**
         * The input stream to be filtered.
         */
        protected volatile InputStream in;
        
    

    4、具体装饰类BufferedInputStream

    public
    class BufferedInputStream extends FilterInputStream {
    
        private static int DEFAULT_BUFFER_SIZE = 8192;
    
        /**
         * The maximum size of array to allocate.
         * Some VMs reserve some header words in an array.
         * Attempts to allocate larger arrays may result in
         * OutOfMemoryError: Requested array size exceeds VM limit
         */
        private static int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
    
    

    具体测试

    public static void main(String[] args) throws Exception
    {
            File file = new File("src/test.txt");
            InputStream in0 = new FileInputStream(file);
            InputStream in1 = new BufferedInputStream(new FileInputStream(file));
            InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
    }
    

    我们这里实例化出了三个InputStream的实现类:

    • in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
    • in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
    • in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能

    半透明装饰器模式与全透明装饰器模式

    • 对于半透明装饰器模式,装饰后的类未必有和抽象构件角色同样的接口方法,它可以有自己扩展的方法
    • 对于全透明装饰器模式,装饰后的类有着和抽象构件角色同样的接口方法

    如下面的代码,就是全透明装饰器模式

            File file = new File("src/test.txt");
            InputStream in0 = new FileInputStream(file);
            InputStream in1 = new BufferedInputStream(new FileInputStream(file));
    

    半透明装饰器模式则是

            FileInputStream in3 = new FileInputStream(file);
            BufferedInputStream in4 = new BufferedInputStream(new FileInputStream(file));
            DataInputStream in5 = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
    

    全透明装饰器模式是一种比较理想主义的想法,现实中不太可能出现。

    比如BufferedInputStream吧,我把FileInputStream装饰为BufferedInputStream,难道BufferedInputStream就完全没有自己的行为?

    装饰器模式的作用是动态给对象增加一些功能,而不需要修改对象本身。

    练习

    根据装饰器模式,我们自定义一个装饰器,将所有的字母转成空格

    src/src/test.txt 文本内容

    hello java78879
    

    装饰类

    public class CharacterInputStream extends FilterInputStream {
    
        public CharacterInputStream(InputStream in) {
            super(in);
        }
    
        @Override
        public int read() throws IOException {
            //ASCLL码对照,[97,122] 和 [65,90]是英文字母
            int c = super.read();
            if(c >= 97 && c <= 122 || c >= 65 && c <= 90){
                return 32; //32是空格
            }else{
                return c;
            }
        }
    
    }
    

    测试

            DataInputStream in = new DataInputStream(
                    new CharacterInputStream(
                            new FileInputStream("src/test.txt")));
    
            String str;
            while((str =  in.readLine()) != null){
                System.out.println(str);
            }
    

    测试结果

              78879
    

    总结

    优点:
    • 扩展功能的方式比较灵活;
      • 装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
    • 每一个装饰器相互独立,需要修改时不会互相影响。
    缺点:
    • 多层装饰比较复杂,就像 Java IO 流,对于初学者不友好。

    代码下载 github

  • 相关阅读:
    C++ 对象模型学习记录(2) 第3章 data语义学
    C++ 对象模型学习记录(1) 第2章 构造函数语义学
    C ++ 对象模型学习记录(4) function 语义学 (未完待续)
    C++ 对象模型学习记录(3) 第1章 关于对象(未完)
    设计模式复习 之 代理模式
    大数运算
    effective C ++ 学习笔记之 item 31 将文件间的编译依赖关系降至最低(未完成)
    Java 复习 之1 多线程
    SQL中char varchar nchar nvarchar ntext区别和使用(资料汇总)
    .Net中的加密解密
  • 原文地址:https://www.cnblogs.com/zhenghengbin/p/9220007.html
Copyright © 2011-2022 走看看