代码改变世界

装饰者模式

2012-11-22 22:28  ...平..淡...  阅读(230)  评论(0编辑  收藏  举报

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都继承到相同的行为。然而,如果能够利用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。通过动态地组合对象,可以写新的代码添加新功能,而无须修改现有代码。既然没有改变现有代码,那么引进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.装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。