`

研磨设计模式之装饰模式-3

 
阅读更多

3  模式讲解

3.1  认识装饰模式

(1)模式功能
        装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观。当装饰过后,从外部使用系统的角度看,就不再是使用原始的那个对象了,而是使用被一系列的装饰器装饰过后的对象。
        这样就能够灵活的改变一个对象的功能,只要动态组合的装饰器发生了改变,那么最终所得到的对象的功能也就发生了改变。
        变相的还得到了另外一个好处,那就是装饰器功能的复用,可以给一个对象多次增加同一个装饰器,也可以用同一个装饰器装饰不同的对象。


(2)对象组合
        前面已经讲到了,一个类的功能的扩展方式,可以是继承,也可以是功能更强大、更灵活的对象组合的方式。
        其实,现在在面向对象设计中,有一条很基本的规则就是“尽量使用对象组合,而不是对象继承”来扩展和复用功能。装饰模式的思考起点就是这个规则,可能有些朋友还不太熟悉什么是“对象组合”,下面介绍一下“对象组合”。
什么是对象组合
        直接举例来说吧,假若有一个对象A,实现了一个a1的方法,而C1对象想要来扩展A的功能,给它增加一个c11的方法,那么一个方案是继承,A对象示例代码如下:

Java代码  收藏代码
  1. public class A {  
  2.     public void a1(){  
  3.         System.out.println("now in A.a1");  
  4.     }  
  5. }  

 

C1对象示例代码如下:

Java代码  收藏代码
  1. public class C1 extends A{  
  2.     public void c11(){  
  3.         System.out.println("now in C1.c11");  
  4.     }  
  5. }  

 

    另外一个方案就是使用对象组合,怎么组合呢?就是在C1对象里面不再继承A对象了,而是去组合使用A对象的实例,通过转调A对象的功能来实现A对象已有的功能,写个新的对象C2来示范,示例代码如下:

Java代码  收藏代码
  1. public class C2 {  
  2.     /** 
  3.      * 创建A对象的实例 
  4.      */  
  5.     private A a = new A();  
  6.   
  7.     public void a1(){  
  8.         //转调A对象的功能  
  9.         a.a1();  
  10.     }  
  11.     public void c11(){  
  12.         System.out.println("now in C2.c11");  
  13.     }  
  14. }   

 

大家想想,在转调前后是不是还可以做些功能处理呢?对于A对象是不是透明的呢?

对象组合是不是也很简单,而且更灵活了:

  • 首先可以有选择的复用功能,不是所有A的功能都会被复用,在C2中少调用几个A定义的功能就可以了;
  • 其次在转调前后,可以实现一些功能处理,而且对于A对象是透明的,也就是A对象并不知道在a1方法处理的时候被追加了功能;
  • 还有一个额外的好处,就是可以组合拥有多个对象的功能,假如还有一个对象B,而C2也想拥有B对象的功能,那很简单,再增加一个方法,然后转调B对象就好了,B对象示例如下:
    Java代码  收藏代码
    1. public class B {  
    2.     public void b1(){  
    3.         System.out.println("now in B.b1");  
    4.     }  
    5. }  

同时拥有A对象功能,B对象的功能,还有自己实现的功能的C3对象示例代码如下:

Java代码  收藏代码
  1. public class C3 {  
  2.     private A a = new A();  
  3.     private B b = new B();  
  4.   
  5.     public void a1(){  
  6.         //转调A对象的功能  
  7.         a.a1();  
  8.     }  
  9.     public void b1(){  
  10.         //转调B对象的功能  
  11.         b.b1();  
  12.     }  
  13.     public void c11(){  
  14.         System.out.println("now in C3.c11");  
  15.     }  
  16. }  

  最后再说一点,就是关于对象组合中,何时创建被组合对象的实例

  • 一种方案是在属性上直接定义并创建需要组合的对象实例
  • 另外一种方案是在属性上定义一个变量,来表示持有被组合对象的实例,具体实例从外部传入,也可以通过IoC/DI容器来注入


示例如下:

Java代码  收藏代码
  1. public class C4 {  
  2.     //示例直接在属性上创建需要组合的对象  
  3.     private A a = new A();  
  4.   
  5.     //示例通过外部传入需要组合的对象  
  6.     private B b = null;  
  7.     public void setB(B b){  
  8.         this.b = b;  
  9.     }  
  10.     public void a1(){  
  11.         //转调A对象的功能  
  12.         a.a1();  
  13.     }  
  14.     public void b1(){  
  15.         //转调B对象的功能  
  16.         b.b1();  
  17.     }  
  18.     public void c11(){  
  19.         System.out.println("now in C4.c11");  
  20.     }  
  21. }  

 

 (3)装饰器
        装饰器实现了对被装饰对象的某些装饰功能,可以在装饰器里面调用被装饰对象的功能,获取相应的值,这其实是一种递归调用。
        在装饰器里不仅仅是可以给被装饰对象增加功能,还可以根据需要选择是否调用被装饰对象的功能,如果不调用被装饰对象的功能,那就变成完全重新实现了,相当于动态修改了被装饰对象的功能。
         另外一点,各个装饰器之间最好是完全独立的功能,不要有依赖,这样在进行装饰组合的时候,才没有先后顺序的限制,也就是先装饰谁和后装饰谁都应该是一样的,否则会大大降低装饰器组合的灵活性。


(4)装饰器和组件类的关系
         装饰器是用来装饰组件的,装饰器一定要实现和组件类一致的接口,保证它们是同一个类型,并具有同一个外观,这样组合完成的装饰才能够递归的调用下去。
        组件类是不知道装饰器的存在的,装饰器给组件添加功能是一种透明的包装,组件类毫不知情。需要改变的是外部使用组件类的地方,现在需要使用包装后的类,接口是一样的,但是具体的实现类发生了改变。

(5)退化形式
        如果仅仅只是想要添加一个功能,就没有必要再设计装饰器的抽象类了,直接在装饰器里面实现跟组件一样的接口,然后实现相应的装饰功能就可以了。但是建议最好还是设计上装饰器的抽象类,这样有利于程序的扩展。


3.2  Java中的装饰模式应用

1:Java中典型的装饰模式应用——I/O流
        装饰模式在Java中最典型的应用,就是I/O流,简单回忆一下,如果使用流式操作读取文件内容,会怎么实现呢,简单的代码示例如下:

Java代码  收藏代码
  1. public class IOTest {  
  2.     public static void main(String[] args)throws Exception  {  
  3.         //流式读取文件  
  4.         DataInputStream din = null;  
  5.         try{  
  6.             din = new DataInputStream(  
  7.                 new BufferedInputStream(  
  8.                         new FileInputStream("IOTest.txt")  
  9.                 )  
  10.             );  
  11.             //然后就可以获取文件内容了  
  12.             byte bs []= new byte[din.available()];   
  13.             din.read(bs);  
  14.             String content = new String(bs);  
  15.             System.out.println("文件内容===="+content);  
  16.         }finally{  
  17.             din.close();  
  18.         }         
  19.     }  
  20. }  

        仔细观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理过后,再把处理过后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。
        可能有朋友会问,装饰器和具体的组件类是要实现同样的接口的,上面这些类是这样吗?看看Java的I/O对象层次图吧,由于Java的I/O对象众多,因此只是画出了InputStream的部分,而且由于图的大小关系,也只是表现出了部分的流,具体如图4所示:

 

                         图4  Java的I/O的InputStream部分对象层次图

 


 查看上图会发现,它的结构和装饰模式的结构几乎是一样的:

  • InputStream就相当于装饰模式中的Component。
  • 其实FileInputStream、ObjectInputStream、StringBufferInputStream这几个对象是直接继承了InputSream,还有几个直接继承InputStream的对象,比如:ByteArrayInputStream、PipedInputStream等。这些对象相当于装饰模式中的ConcreteComponent,是可以被装饰器装饰的对象。
  • 那么FilterInputStream就相当于装饰模式中的Decorator,而它的子类DataInputStream、BufferedInputStream、LineNumberInputStream和PushbackInputStream就相当于装饰模式中的ConcreteDecorator了。另外FilterInputStream和它的子类对象的构造器,都是传入组件InputStream类型,这样就完全符合前面讲述的装饰器的结构了。

        同样的,输出流部分也类似,就不去赘述了。
        既然I/O流部分是采用装饰模式实现的,也就是说,如果我们想要添加新的功能的话,只需要实现新的装饰器,然后在使用的时候,组合进去就可以了,也就是说,我们可以自定义一个装饰器,然后和JDK中已有的流的装饰器一起使用。能行吗?试试看吧,前面是按照输入流来讲述的,下面的示例按照输出流来做,顺便体会一下Java的输入流和输出流在结构上的相似性。


2:自己实现的I/O流的装饰器——第一版
        来个功能简单点的,实现把英文加密存放吧,也谈不上什么加密算法,就是把英文字母向后移动两个位置,比如:a变成c,b变成d,以此类推,最后的y变成a,z就变成b,而且为了简单,只处理小写的,够简单的吧。
        好了,还是看看实现简单的加密的代码实现吧,示例代码如下:

Java代码  收藏代码
  1. /** 
  2.  * 实现简单的加密 
  3.  */  
  4. public class EncryptOutputStream  extends OutputStream{  
  5.     //持有被装饰的对象  
  6.     private OutputStream os = null;  
  7.     public EncryptOutputStream(OutputStream os){  
  8.         this.os = os;  
  9.     }     
  10.     public void write(int a) throws IOException {  
  11.         //先统一向后移动两位  
  12.         a = a+2;  
  13.         //97是小写的a的码值  
  14.         if(a >= (97+26)){  
  15.             //如果大于,表示已经是y或者z了,减去26就回到a或者b了  
  16.             a = a-26;  
  17.         }  
  18.         this.os.write(a);  
  19.     }  
  20. }  

 测试一下看看,好用吗?客户端使用代码示例如下:

Java代码  收藏代码
  1. public class Client {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //流式输出文件  
  4.         DataOutputStream dout = new DataOutputStream(  
  5.             new BufferedOutputStream(  
  6.                 //这是我们加的装饰器  
  7.                 new EncryptOutputStream(  
  8.                     new FileOutputStream("MyEncrypt.txt"))));  
  9.         //然后就可以输出内容了  
  10.         dout.write("abcdxyz".getBytes());  
  11.         dout.close();  
  12.     }  
  13. }  

 运行一下,打开生成的文件,看看结果,结果示例如下:

Java代码  收藏代码
  1. cdefzab  

        很好,是不是被加密了,虽然是明文的,但已经不是最初存放的内容了,一切显得非常的完美。
        再试试看,不是说装饰器可以随意组合吗,换一个组合方式看看,比如把BufferedOutputStream和我们自己的装饰器在组合的时候换个位,示例如下:

Java代码  收藏代码
  1. public class Client {  
  2.     public static void main(String[] args) throws Exception {  
  3.         //流式输出文件  
  4.         DataOutputStream dout = new DataOutputStream(  
  5.             //换了个位置  
  6.             new EncryptOutputStream (  
  7.                 new BufferedOutputStream(  
  8.                     new FileOutputStream("MyEncrypt.txt"))));  
  9.         dout.write("abcdxyz".getBytes());  
  10.         dout.close();  
  11.     }  
  12. }  

        再次运行,看看结果。坏了,出大问题了,这个时候输出的文件一片空白,什么都没有。这是哪里出了问题呢?
        要把这个问题搞清楚,就需要把上面I/O流的内部运行和基本实现搞明白,分开来看看具体的运行过程吧。


(1)先看看成功输出流中的内容的写法的运行过程:

 

  • 当执行到“dout.write("abcdxyz".getBytes());”这句话的时候,会调用DataOutputStream的write方法,把数据输出到BufferedOutputStream中;
  • 由于BufferedOutputStream流是一个带缓存的流,它默认缓存8192byte,也就是默认流中的缓存数据到了8192byte,它才会自动输出缓存中的数据;
  • 而目前要输出的字节肯定不到8192byte,因此数据就被缓存在BufferedOutputStream流中了,而不会被自动输出
  • 当执行到“dout.close();”这句话的时候:会调用关闭DataOutputStream流,这会转调到传入DataOutputStream中的流的close方法,也就是BufferedOutputStream的close方法,而BufferedOutputStream的close方法继承自FilterOutputStream,在FilterOutputStream的close方法实现里面,会先调用输出流的方法flush,然后关闭流。也就是此时BufferedOutputStream流中缓存的数据会被强制输出;
  • BufferedOutputStream流中缓存的数据被强制输出到EncryptOutputStream流,也就是我们自己实现的流,没有缓存,经过处理后继续输出;
  • EncryptOutputStream流会把数据输出到FileOutputStream中,FileOutputStream会直接把数据输出到文件中,因此,这种实现方式会输出文件的内容。

 

 

(2)再来看看不能输出流中的内容的写法的运行过程:

  • 当执行到“dout.write("abcdxyz".getBytes());”这句话的时候,会调用DataOutputStream的write方法,把数据输出到EncryptOutputStream中;
  • EncryptOutputStream流,也就是我们自己实现的流,没有缓存,经过处理后继续输出,把数据输出到BufferedOutputStream中;
  • 由于BufferedOutputStream流是一个带缓存的流,它默认缓存8192byte,也就是默认流中的缓存数据到了8192byte,它才会自动输出缓存中的数据;
  • 而目前要输出的字节肯定不到8192byte,因此数据就被缓存在BufferedOutputStream流中了,而不会被自动输出
  • 当执行到“dout.close();”这句话的时候:会调用关闭DataOutputStream流,这会转调到传入DataOutputStream流中的流的close方法,也就是EncryptOutputStream的close方法,而EncryptOutputStream的close方法继承自OutputStream,在OutputStream的close方法实现里面,是个空方法,什么都没有做。因此,这种实现方式没有flush流的数据,也就不会输出文件的内容,自然是一片空白了。

3:自己实现的I/O流的装饰器——第二版

        要让我们写的装饰器跟其它Java中的装饰器一样用,最合理的方案就应该是:让我们的装饰器继承装饰器的父类,也就是FilterOutputStream类,然后使用父类提供的功能来协助完成想要装饰的功能。示例代码如下:

 

 

public class EncryptOutputStream2  extends FilterOutputStream{

    private OutputStream os = null;

    public EncryptOutputStream2(OutputStream os){

       //调用父类的构造方法

       super(os);

    }

    public void write(int a) throws IOException {

       //先统一向后移动两位

       a = a+2;

       //97是小写的a的码值

       if(a >= (97+26)){

           //如果大于,表示已经是y或者z了,减去26就回到a或者b

           a = a-26;

       }

       //调用父类的方法

       super.write(a);

    }

}

 

再测试看看,是不是跟其它的装饰器一样,可以随便换位了呢?

 

 

 

 

未完待续

注:本文转自   http://chjavach.iteye.com

 

分享到:
评论

相关推荐

    研磨设计模式源码

    《研磨设计模式源码》是一份非常宝贵的资源,它提供了设计模式的实践代码,帮助开发者深入理解并应用这些模式。设计模式是软件工程中经过长期实践总结出来的一套通用解决方案,它们描述了在特定场景下如何解决常见...

    研磨设计模式-配套源代码 UTF-8格式

    《研磨设计模式》是一本深入探讨软件设计原则与实践的经典书籍,其配套源代码提供了丰富的实例,帮助读者更好地理解和应用各种设计模式。这个UTF-8格式的压缩包包含了书中介绍的各种设计模式的实现,是学习和研究...

    研磨设计模式(完整带书签).part2.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-配套源代码

    以上只是设计模式中的一部分,研磨设计模式的配套源代码可能涵盖了这些或更多的模式。通过学习这些源代码,你可以更深入地理解每个模式的实现细节,以及如何在实际项目中灵活应用。这将有助于提升你的编程技能,使...

    研磨设计模式PDF

    《研磨设计模式》这本书是陈臣和王斌两位作者合作的成果,专注于讲解软件设计中的模式应用。设计模式是软件工程中的一种最佳实践,它总结了在特定上下文中解决问题的常见方法,使得开发者可以复用这些解决方案,提高...

    研磨设计模式-配套源代码.7z

    《研磨设计模式》是一本深入探讨软件设计模式的书籍,其配套源代码包含了许多经典设计模式的实际应用示例。这些源代码可以帮助读者更直观地理解设计模式的原理和使用方法,进一步提升软件开发能力。 设计模式是软件...

    研磨设计模式[书签]

    《研磨设计模式》是由陈臣和王斌合著,由清华大学出版社出版的一本深入探讨设计模式的专业书籍。设计模式是软件工程中的一个重要概念,它代表了在特定上下文中解决问题的常见方法,经过时间和实践的验证,具有很高的...

    研磨设计模式-配套源代码.rar

    《研磨设计模式》是一本深入探讨软件设计模式的书籍,配套源代码是作者为了帮助读者更好地理解和应用书中介绍的设计模式而提供的实践示例。设计模式是软件开发中经过实践检验的、解决常见问题的模板,它为软件设计...

    研磨设计模式.rar

    《研磨设计模式》这本书是软件开发领域中的经典之作,主要关注的是面向对象设计中的设计模式。设计模式是在特定上下文中解决常见问题的最佳实践,它为开发者提供了在类似情况下重复使用解决方案的模板,有助于提高...

    研磨设计模式全部源代码

    这个压缩包“研磨设计模式全部源代码”包含了多种设计模式的实现,这些模式可以帮助开发者写出更可维护、可扩展和可复用的代码。下面将详细讲解其中可能包含的一些重要设计模式及其应用。 1. 工厂模式:这是最简单...

    研磨设计模式博文集

    这个“研磨设计模式博文集”显然是一份深入探讨设计模式的资料集合,其中可能包含了对多种设计模式的详细解析、示例代码以及实际应用中的经验分享。在软件开发中,设计模式能够帮助开发者提高代码质量、可读性和可...

    研磨设计模式讲课PPT

    研磨设计模式是一本深入探讨软件设计原则与实践的书籍,其讲课PPT为我们提供了丰富的设计模式知识。设计模式是软件工程中经过实践验证的、解决常见问题的模板,是经验丰富的开发人员智慧的结晶。这些模式可以帮助...

    研磨设计模式 源代码

    《研磨设计模式》是一本深入探讨软件设计模式的经典书籍,源代码包含了书中所讲解的各种设计模式的实际应用示例。设计模式是软件工程中的重要概念,它们是经过反复验证、在特定情境下解决常见问题的有效解决方案。...

    研磨设计模式-part3

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式(完整带书签).part1.pdf

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式 演示源代码

    "研磨设计模式 演示源代码"这个资源包含了对设计模式的详细解释和实例分析,旨在帮助学习者深入理解和应用这些模式。 1. **单例模式**:确保一个类只有一个实例,并提供一个全局访问点。在资源管理、缓存或者线程池...

    研磨设计模式-part2

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式-part4

    《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,...

    研磨设计模式视频课程PPT

    这个名为“研磨设计模式视频课程PPT”的压缩包包含了一份关于23种核心设计模式的详细教学资料,旨在帮助开发者提升软件设计的效率和可维护性。下面将对这些设计模式进行深入解析。 1. **单例模式(Singleton)**:...

    X-gen PPT下载——《研磨设计模式》 实战

    《研磨设计模式》实战是IT领域中关于软件设计的一份重要资料,它主要探讨了设计模式在实际项目中的应用。设计模式是软件工程中经过长期实践总结出的通用问题解决方案,是解决常见设计问题的经验总结。这份PPT可能是...

Global site tag (gtag.js) - Google Analytics