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