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. }
public class A {public void a1(){System.out.println("now in A.a1");}
}

C1对象示例代码如下:

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

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

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

大家想想,在转调前后是不是还可以做些功能处理呢?对于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. }
    public class B {public void b1(){System.out.println("now in B.b1");}
    }
    

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

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

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

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

示例如下:

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

 (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. }
public class IOTest {public static void main(String[] args)throws Exception  {//流式读取文件DataInputStream din = null;try{din = new DataInputStream(new BufferedInputStream(new FileInputStream("IOTest.txt")));//然后就可以获取文件内容了byte bs []= new byte[din.available()]; din.read(bs);String content = new String(bs);System.out.println("文件内容===="+content);}finally{din.close();}      }
}

仔细观察上面的代码,会发现最里层是一个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. }
/*** 实现简单的加密*/
public class EncryptOutputStream  extends OutputStream{//持有被装饰的对象private OutputStream os = null;public EncryptOutputStream(OutputStream os){this.os = 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;}this.os.write(a);}
}

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

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. }
public class Client {public static void main(String[] args) throws Exception {//流式输出文件DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(//这是我们加的装饰器new EncryptOutputStream(new FileOutputStream("MyEncrypt.txt"))));//然后就可以输出内容了dout.write("abcdxyz".getBytes());dout.close();}
}

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

Java代码
  1. cdefzab
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. }
public class Client {public static void main(String[] args) throws Exception {//流式输出文件DataOutputStream dout = new DataOutputStream(//换了个位置new EncryptOutputStream (new BufferedOutputStream(new FileOutputStream("MyEncrypt.txt"))));dout.write("abcdxyz".getBytes());dout.close();}
}

再次运行,看看结果。坏了,出大问题了,这个时候输出的文件一片空白,什么都没有。这是哪里出了问题呢?
        要把这个问题搞清楚,就需要把上面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/blog/780979

研磨设计模式之装饰模式-3(转)相关推荐

  1. Java私塾:研磨设计模式 之 装饰模式(Decorator)3

    22.3  模式讲解 22.3.1  认识装饰模式 (1)模式功能 装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观.当装饰过后,从外部使用系统的角度看 ...

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

    3  模式讲解 3.1  认识装饰模式 (1)模式功能         装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观.当装饰过后,从外部使用系统的角 ...

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

    3  模式讲解 3.1  认识装饰模式 (1)模式功能        装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象增加功能,相当于是改变了对象的外观.当装饰过后,从外部使用系统的角度 ...

  4. 研磨设计模式之 策略模式--转

    http://www.uml.org.cn/sjms/201009092.asp 研磨设计模式之 策略模式   2010-09-09 作者:云飞龙行 来源:云飞龙行的blog   先感谢众多朋友的支持 ...

  5. 研磨设计模式 之 访问者模式(Visitor)2——跟着cc学设计系列

    25.2  解决方案 25.2.1  访问者模式来解决 用来解决上述问题的一个合理的解决方案,就是使用访问者模式.那么什么是访问者模式呢? (1)访问者模式定义 (2)应用访问者模式来解决的思路 仔细 ...

  6. 研磨设计模式 之 组合模式(Composite) 3——跟着cc学设计系列

    15.3  模式讲解 15.3.1  认识组合模式 (1)组合模式的目的 组合模式的目的是:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作. 实现这个目标的关键之处,是设计一 ...

  7. 设计模式之装饰模式20170726

    结构型设计模式之装饰模式: 一.含义 动态地给一个对象添加一些额外的职责.就增加功能来说,装饰模式相比生成子类更为灵活. 通俗来讲,装饰模式是对类的功能进行加强或减弱. 二.代码说明 1.主要有两个角 ...

  8. java设计模式之装饰模式_Java中的装饰器设计模式

    java设计模式之装饰模式 装饰器设计模式允许在运行时将附加职责或行为动态附加到对象. 它是一种结构模式,利用聚合来组合这些行为. 在本教程中,我们将学习实现装饰器模式. UML图: 让我们从装饰器模 ...

  9. java观察者模式本质_6.[研磨设计模式笔记]观察者模式

    1.定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新. 2.解决问题 --订阅报纸 看起来订阅者是直接根有据打交道,但实际上,订阅者的订阅数据 ...

最新文章

  1. Sorenson Capital:值得投资的 5 种 AI 技术
  2. 在Delphi程序中操作注册表
  3. Zookeeper集群搭建分布式
  4. Python—实训day10—Matplotlib数据可视化和scikit-learn构建模型
  5. spring framework体系结构及内部各模块jar之间的maven依赖关系
  6. PHP付费资源下载交易平台网站源码
  7. [转载]linux下上传文件真的需要ftp么?
  8. 摄像头安装说明_老司机告诉你马路上不同摄像头有不同作用,注意区分小心扣分...
  9. linux声卡测试命令,linux添加声卡驱动使用命令行音频播放器的方法
  10. 《Python游戏趣味编程》 第6章 见缝插针
  11. oracle脑裂的判断机制,Keepalived两节点出现双VIP的情况(脑裂)
  12. Qt + GDAL 写入矢量图层 shp
  13. 知乎上关于ReactNative的评论汇总(网友们有才哟...)
  14. jink Art儿童摄影师视频教程中文字幕
  15. 用html作小米官网首页效果,html+css实现小米商城首页静态页面
  16. 微信小程序,仓库系统,可以扫描入库查询
  17. Mencoder常用视频转换参数
  18. selenium打不开Ie浏览器的解决办法
  19. 学习软件的一点心得体会
  20. 荣耀智慧屏评测 鸿蒙OS加持,荣耀智慧屏评测:鸿蒙OS加持 面向未来的超智能电视...

热门文章

  1. EJB3创建Timer
  2. 深入浅出通信原理知识点3
  3. 获取一个APP里面的图片素材
  4. 基于阿里云IoT平台OTA进行APP确认升级的方案——业务架构类
  5. 计算机组成原理实验心得200字,2016年江西师范大学考研专业目录及考试科目
  6. WaveTone 2.67原创汉化版扒谱辅助教程
  7. 串口开发 打印机 读卡器 遇到的问题
  8. 配置小程序开发者工具及其使用(下)
  9. Dubbo+Flutter在线交友平台教程第四天 圈子功能实现
  10. stm32实现呼吸灯