zoukankan      html  css  js  c++  java
  • 装饰者模式

    利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进bug或产生意外副作用的机会将大幅度减少。

    设计原则

    类应该对扩展开发,对修改关闭。

    定义

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

     类图:

    写个具体的例子(蛋糕~~)来演示下:

    //Cake类
    public abstract class Cake {
        public String descriprion = "Cake";
        public abstract double cost();
        
        public String display() {
            return descriprion+":";
        }
        //其他方法
    }
    
    //CakeA类
    public class CakeA extends Cake{
        public  CakeA() {
            descriprion = "CakeA";
        }
        @Override
        public double cost() {
            return 10;
        }
    }
    
    //CakeB类
    public class CakeB extends Cake{
        public  CakeB() {
            descriprion = "CakeB";
        }
        @Override
        public double cost() {
            return 15;
        }
    }
    
    //装饰者抽象类(Decorator类)
    //作为装饰者的抽象类,供具体装饰者继承
    public abstract class Decorator extends Cake{
        //其他方法
    }
    
    // Chocolate类
    public class Chocolate extends Decorator {
        private Cake cake;
        public Chocolate(Cake cake) {
            this.cake = cake;
        }
        public String display() {
            return cake.display() + "add Chocolate, ";
        }
        public double cost() {
            return cake.cost() + 2.0;
        }
    }
    
    // Milk类
    public class Milk extends Decorator {
        private Cake cake;
        public Milk(Cake cake) {
            this.cake = cake;
        }
        public String display() {
            return cake.display() + "add Milk, ";
        }
        public double cost() {
            return cake.cost() + 1.0;
        }
    }
    
    // Nut类
    public class Nut extends Decorator {
        private Cake cake;
        public Nut(Cake cake) {
            this.cake = cake;
        }
        public String display() {
            return cake.display() + "add Nut, ";
        }
        public double cost() {
            return cake.cost() + 1.5;
        }
    }
    
    
    //Main类
    public class Main {
        public static void main(String[] args) {
            //定义蛋糕角色
            Cake cake1 = new CakeA();
            Cake cake2 = new CakeB();
            
            //定义其他装饰
            Decorator decorator1 = new Nut(new Milk(new Nut(new Milk(cake1))));
            Decorator decorator2 = new Chocolate(new Milk(cake2));
            
            //输出效果
            System.out.println(decorator1.display()+"$"+decorator1.cost());
            System.out.println(decorator2.display()+"$"+decorator2.cost());
        }
    }
    //输出结果
    CakeA:add Milk, add Nut, add Milk, add Nut, $15.0
    CakeB:add Milk, add Chocolate, $18.0

    如果此时加入了另外的装饰属性,比如蛋糕的大小

    (1)  那么如果开始的时候是用具体的蛋糕子类来继承Cake类的话,那此时为了加入蛋糕大小的属性,就必须修改每一个具体的子类

    (2)  反之,如果使用了装饰者模式,那么我们可以定义一个装饰者子类,用来描述大小,而不用修改原来的类。这样就做到了对扩展开放,对修改关闭的原则。

    在Main类中只用对具体操作稍加修改:

    Decorator decorator1 = new Nut(new Milk(new Nut(new Milk(new MaxSize(cake1)))));
    Decorator decorator2 = new Chocolate(new Milk(new MediumSize(cake2)));
    //输出结果
    CakeA:MaxCup, add Milk, add Nut, add Milk, add Nut, $18.0
    CakeB:MediumCup, add Milk, add Chocolate, $21.0

    java设计中很经典的装饰者模型,java.io


    在java类库中的IO流就是用装饰者模式设计的。JDK5.0中60多个IO流类组成了四大家族:InputStream,OutputStream,Reader,Writer。 

    InputStream/OutputStream是对字节序列进行操作的抽象类。 
    Reader/Writer是基于Unicode代码单元进行操作的抽象类。 
    


    这四大家族中大量的类都具有自己不同的功能,要做到方便地完成各种输入输出行为,必须组合使用这些类,装饰者模式是再好不过的设计了。那么IO类库如何实现装饰者模式的,我们看看几个类的部分源码: 

    Java代码

    //InputStream:字节序列输入类鼻祖  
    public abstract class InputStream implements Closeable {   
        //最基本的读取字节的抽象方法,供子类扩展。   
        public abstract int read() throws IOException;  
     }   
        //FileInputStream: 读取文件中的字节流类 继承InputStream  
    public class FileInputStream extends InputStream{   
       //构造器  
         public FileInputStream(String name) throws FileNotFoundException{   
        //.......  
        }   
      //本地方法,与操作系统低层交互的具体读入方法  
       public native int read() throwsIOException;   
    }   
      //FilterInputStream: 过滤流类,起装饰器作用,用于对输入装配各种功能   
    public class FilterInputStream extends InputStream {   
       //用于记录被装饰者,也就是需要装配新功能的InputStream对象   
       protected volatile InputStream in;  
       protected FilterInputStream(InputStream in) {   
       //构造装饰器   
        this.in = in;   
       //设置需要被包装InputStream对象  
       }   
      //读入字节   
       public int read() throws IOException {   
          return in.read();   
       }   
    }   
    //BufferedInputStream: 使输入流具有缓冲功能,是一种可以装配缓冲功能的装饰器,继承FilterInputStream   
    public class BufferedInputStream extends FilterInputStream {   
    //构造器   
       public BufferedInputStream(InputStream in) {   
        this(in, defaultBufferSize);   
       //in就是被装配缓冲功能的InputStream   
      }   
    }   

    这四个类同属于InputStream家族,他们就是一个经典的装饰器模式设计。其中 

    InputStream 具有读入功能的抽象被装饰器。 
    FileInputStream  具有读入文件功能的具体被装饰器 
    FilterInputStream  具备装饰器的抽象意义。 
    BufferedInputStream   具有具体功能(缓冲功能)的装饰器。 

     

    这个时候,如果我想设计一个具有缓冲功能读取文件中的字节的行为,就可以这样设计:

    public void IOTest{   
      //缓冲装饰器包装文件字节输入流   
      BufferedInputStream bis=new BufferedInputStream(new FileInputStream("C://decorator.txt"));   
      //读取内容   
        bis.read();   
    }  

    IO类库中还有很多其他的装饰器,比如处理基本数据类型的DataInputStream,处理ZIP文件流的ZipInputStream,等等。只要我们想的到的行为,都可以用这些装饰器包装组合来完成。就这一点,装饰器绝对是Perfect。

    装饰者模式的应用场景

    1.当我们需要为某个现有的对象,动态的增加一个新的功能或职责时,可以考虑使用装饰模式。

    2.适应于某个对象的职责经常发生变化或者经常需要动态的增加职责,避免因为这种为了适应这样的变化,而增加继承子类扩展的方式,因为这种方式为 造成,子类膨胀的速度过快,难以控制。

    要点

    1.继承属于扩展形式之一,但不见得是达到弹性设计的最佳方式。

    2.在我们的设计中,应该允许行为可以被扩展,而无须修改现在的代码。

    3.装饰者和被装饰者对象有相同的超类型

    4.装饰者可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。

    5.对象可以在任何时候被装饰,所以可以在运行时动态的,不限量的用你喜欢的装饰者来装饰对象。

    6.装饰者模式实际上还是使用了继承,但是重点不同。装饰者模式是利用继承达到“类型匹配”的目的,而不是利用继承获得其“行为”。

    7.那么至于装饰者模式的行为,它不是继承自超类,而是由组合对象得来的。

    8.如果使用具体的继承,那么类的行为只能在编译时静态决定。换言之,就是说行为要么来自超类,要么来自子类。反之,利用了组合,将使得行为在运行时才被决定,而且是组合而成的。

    9.因此可以在任何时候,实现新的装饰者增加新的行为。然后直接用它来装饰被装饰者。如果是依赖继承,那么当需要新行为时,就必须修改现有的代码。

    10.多用组合,少用继承。

    11. 针对接口编程,不针对实现编程。

    12.对扩展开放,对修改关闭。

    13.装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。在实际项目中可以根据需要为装饰者添加新的行为,做到“半透明”装饰者。 

    缺点:

    1.类太多(Java I/O库)

    2.有些代码依赖特定的类型,此时,使用装饰者模式需要小心。

    3.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

  • 相关阅读:
    取得窗口大小和窗口位置兼容所有浏览器的js代码
    一个简单易用的导出Excel类
    如何快速启动chrome插件
    网页表单设计案例
    Ubuntu下的打包解包
    The source file is different from when the module was built. Would you like the debugger to use it anyway?
    FFisher分布
    kalman filter
    Group delay Matlab simulate
    24位位图格式解析
  • 原文地址:https://www.cnblogs.com/chenbin7/p/2783429.html
Copyright © 2011-2022 走看看