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.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

  • 相关阅读:
    正则表达式简介
    PHP中简单的页面缓冲技术
    PHP 程序加速探索
    PHP中通过Web执行C/C++应用程序
    PHP实现聊天室的主动更新与被动更新
    php中Cookie及其使用
    Linux 下 PHP 连接 MS SQLServer 的办法
    网站加速 PHP 缓冲的免费实现方法
    Spark Streaming中的基本操作函数实例
    Scala中的s函数
  • 原文地址:https://www.cnblogs.com/chenbin7/p/2783429.html
Copyright © 2011-2022 走看看