装饰者模式 (Decorator)
动态地给一个对象添加一些额外的职责。是一种结构式模式,就增加功能来说,Decorator 模式相比生成子类更为灵活。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。装饰模式可以在不使用创造更多子类的情况下,将对象的功能加以扩展。
要点: 装饰者与被装饰者拥有共同的超类,继承的目的是继承类型,而不是行为.
装饰模式的类图如下:
在装饰模式中的角色有:
● 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
● 具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
● 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
● 具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。
代码说明:
抽象构件角色:
public interface Component{
public void sampleOperation();
}
具体挂件角色:
public class ConcreteComponent implements Component{
@Override
public void sampleOperation() {
System.out.println("初始行为");
}
}
装饰角色:
public class Decorator implements Component{
private Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void sampleOperation() {
// 委派给 构件
component.sampleOperation();
}
}
具体装饰角色
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void sampleOperation() {
super.sampleOperation();
// 其他 可扩展的行为
}
}
实例说明(齐天大圣的例子):
孙悟空有七十二般变化,他的每一种变化都给他带来一种附加的本领。他变成鱼儿时,就可以到水里游泳;他变成鸟儿时,就可以在天上飞行。
本例中,Component 的角色便由鼎鼎大名的齐天大圣扮演;ConcreteComponent 的角色属于大圣的本尊,就是猢狲本人;Decorator 的角色由大圣的七十二变扮演。而 ConcreteDecorator 的角色便是鱼儿、鸟儿等七十二般变化。
抽象构件角色: 大圣的称号
public interface TheGreatestSage {
public void move();
}
具体构件角色 :“大圣本尊”猢狲类
public class Monkey implements TheGreatestSage{
@Override
public void move() {
System.out.println("Monkey Move");
}
}
抽象 装饰者 角色: “七十二变”
public class Change implements TheGreatestSage{
private TheGreatestSage sage;
public Change(TheGreatestSage sage){
this.sage = sage;
}
@Override
public void move() {
sage.move();
}
}
具体装饰角色 :“鱼儿”
public class Fish extends Change{
public Fish(TheGreatestSage sage) {
super(sage);
}
@Override
public void move() {
System.out.println("Fish Move");
System.out.println("Fish 的 其他行为动作描述");
}
}
具体装饰角色 :“小鸟”
public class Bird extends Change{
public Bird(TheGreatestSage sage) {
super(sage);
}
@Override
public void move() {
System.out.println("Bird Move");
System.out.println("Bird 的 其他行为动作描述");
}
}
测试:
public class Client {
public static void main(String[] args) {
TheGreatestSage sage = new Monkey();
Fish fish = new Fish(sage);
fish.move();
Bird bird = new Bird(fish);
bird.move();
}
}
上面的例子中,系统把大圣从一只猢狲装饰成了一只鸟儿(把鸟儿的功能加到了猢狲身上),然后又把鸟儿装饰成了一条鱼儿(把鱼儿的功能加到了猢狲+鸟儿身上,得到了猢狲+鸟儿+鱼儿)。
装饰模式的简化
大多数情况下,装饰模式的实现都要比上面给出的示意性例子要简单。
如果只有一个ConcreteComponent类,那么可以考虑去掉抽象的Component类(接口),把Decorator作为一个ConcreteComponent子类。如下图所示:
如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。甚至在只有两个ConcreteDecorator类的情况下,都可以这样做。如下图所示:
半透明的 装饰模式
然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而鸟儿有。这就意味着鸟儿应当有一个新的fly()方法。再比如,齐天大圣类并没有游泳的能力,而鱼儿有,这就意味着在鱼儿类里应当有一个新的swim()方法。
这就导致了大多数的装饰模式的实现都是“半透明”的,而不是完全透明的。换言之,允许装饰模式改变接口,增加新的方法。这意味着客户端可以声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法:
TheGreatestSage sage = new Monkey();
Bird bird = new Bird(sage);
bird.fly();
半透明的装饰模式是介于装饰模式和适配器模式之间的。适配器模式的用意是改变所考虑的类的接口,也可以通过改写一个或几个方法,或增加新的方法来增强或改变所考虑的类的功能。大多数的装饰模式实际上是半透明的装饰模式,这样的装饰模式也称做半装饰、半适配器模式。
装饰模式的优点:
(1)装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。
(2)通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
装饰模式的缺点:
由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
-------------------------------------------------------------------------------------------------------------------------------------------------------
设计模式在JAVA I/O库中的应用
装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。
Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。
根据上图可以看出:
● 抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。
● 具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。
● 抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。
● 具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。
例如:
public class IOTest {
public static void main(String[] args) throws IOException {
// 流式读取文件
DataInputStream dis = null;
try{
dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")
)
);
//读取文件内容
byte[] bs = new byte[dis.available()];
dis.read(bs);
String content = new String(bs);
System.out.println(content);
}finally{
dis.close();
}
}
}
观察上面的代码,会发现最里层是一个FileInputStream对象,然后把它传递给一个BufferedInputStream对象,经过BufferedInputStream处理,再把处理后的对象传递给了DataInputStream对象进行处理,这个过程其实就是装饰器的组装过程,FileInputStream对象相当于原始的被装饰的对象,而BufferedInputStream对象和DataInputStream对象则相当于装饰器。
来源:oschina
链接:https://my.oschina.net/u/1387400/blog/760560