装饰器模式的核心思想
Decorator装饰器,顾名思义,就是动态地给一个对象添加一些额外的职责,就好比为房子进行装修一样。因此,装饰器模式具有如下的特征:
Ø 它必须具有一个装饰的对象。
Ø 它必须拥有与被装饰对象相同的接口。
Ø 它可以给被装饰对象添加额外的功能。
用一句话总结就是:保持接口,增强性能。
装饰器通过包装一个装饰对象来扩展其功能,而又不改变其接口,这实际上是基于对象的适配器模式的一种变种。它与对象的适配器模式的异同点如下。
Ø 相同点:都拥有一个目标对象。
Ø 不同点:适配器模式需要实现另外一个接口,而装饰器模式必须实现该对象的接口。
换句话说,对象的适配器模式是把一个对象适配成了另一个对象,而装饰器模式将丰富目标对象的功能但不改变它的接口。如下图所示,对象的适配器模式将一个圆形变成了正方形,而装饰器模式则不改变它的圆形形状,但改变了它的外观颜色。
装饰器模式的模型图如下图所示,共包括如下3个元素。
Ø 接口类Sourcable:定义了目标对象的接口。
Ø 源类Source:是接口Sourcable的一个实现。
Ø 装饰器类:它用来对Source对象进行装饰,但又必须实现Sourcable接口,以不改变Source对象的接口,根据装饰作用的不同,可以拥有多个装饰器类。
为了演示多次装饰的效果,我们这里分别创建了3个装饰器,第一个装饰器Decorator1装饰Source对象,第二个装饰器Decorator2装饰被第一个装饰器装饰后的对象,第三个装饰器Decorator2装饰被第二个装饰器装饰后的对象。
下面来看具体的实现。
(1)Sourcable类定义了一个接口函数operation()。
public interface Sourcable {
public void operation();
}
(2)Source是Sourcable的一个实现,其函数operation()负责往控制台输出一个字符串:原始类的方法。
public class Source implements Sourcable {
public void operation() {
System.out.println("原始类的方法");
}
}
(3)装饰器类Decorator1采用了典型的对象适配器模式,它首先拥有一个Sourcable对象 source,该对象通过构造函数进行初始化。然后它实现了Sourcable.java接口,以期保持与source同样的接口,并在重写的 operation()函数中调用source的operation()函数,在调用前后可以实现自己的输出,这就是装饰器所扩展的功能。
public class Decorator1 implements Sourcable {
private Sourcable source;
public Decorator1(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第1个装饰器装饰前");
source.operation();
System.out.println("第1个装饰器装饰后");
}
}
(4)装饰器类Decorator2是另一个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。
public class Decorator2 implements Sourcable {
private Sourcable source;
public Decorator2(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第2个装饰器装饰前");
source.operation();
System.out.println("第2个装饰器装饰后");
}
}
(5)装饰器类Decorator3是第三个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。
public class Decorator3 implements Sourcable {
private Sourcable source;
public Decorator3(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第3个装饰器装饰前");
source.operation();
System.out.println("第3个装饰器装饰后");
}
}
这时,我们就可以像使用对象的适配器模式一样来使用这些装饰器,使用不同的装饰器就可以达到不同的装饰效果。例如,首先需要创建一个源类对象source,然后根据将对象使用Decorator1来装饰,并以此使用Decorator2、 Decorator3进行装饰,装饰后的对象同样具有与source同样的接口。
public class DecoratorTest {
public static void main(String[] args) {
// 创建源类对象
Sourcable source = new Source();
// 装饰类对象
Sourcable obj =
new Decorator1(
new Decorator2(
new Decorator3(source)
)
);
// 调用目标类的方法
obj.operation();
}
}
运行该程序的输出如下:
第1个装饰器装饰前
第2个装饰器装饰前
第3个装饰器装饰前
原始类的方法
第3个装饰器装饰后
第2个装饰器装饰后
第1个装饰器装饰后
从输出的结果可以看出,原始类对象source依次被Decorator1、Decorator2、Decorator3进行了装饰。
12.2.2 何时使用装饰器模式
由此可见,装饰模式提供了即插即用的方法,在运行期间决定何时增加何种功能。装饰模式与继承关系的目的都是要 扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式让系统动态地决定“贴上”一个需要的“装饰”,或者除掉一个不需要的装饰。而继承则不 同,继承关系是静态的,它在系统运行前就决定了。可以通过使用不同的具体修饰类及这些装饰类的排列组合,设计可以创造更多不同行为的组合。虽然比继承要灵 活,这意味着它比继承更容易出错。
因此,装饰模式一般在以下情况使用:
Ø 需要扩展一个类的功能,或给一个类增加附加责任。
Ø 需要动态地给一个对象增加功能,这些功能可以动态撤销。
Ø 需要增加由一些基本的排列组合产生大量的功能,从而使继承关系变得不现实。
但也需要注意装饰模式的缺点:由于使用装饰模式可以比使用继承关系需要较少的目的类,但是在另一方面,使用装饰模式会产生比使用继承方式更多的对象。这在使用时进行错误查询变得更困难了,特别是这些对象看上去都很像。
Java中的应用—I/O输入/输出流管道的装饰器模式
Java的I/O库提供了一个称做链接(Chaining)的机制,可以将一个流处理器与另一个流处理器首尾相接,以其中之一的输出为输入,形成一个流管道的链接。
例如,DataInputStream流处理器可以把FileInputStream流对象的输出当做输入,将Byte类型的数据转换成Java的原始类型和String类型的数据,如下图所示。
类似地,向一个文件写入Byte类型的数据不是一个简单的过程。一个程序需要向一个文件里写入的数据往往都是 结构化的,而Byte类型则是原始类型。因此在写的时候必须经过转换。DataOutputStream流处理器可以接收原始数据类型和String数据 类型,而这个流处理器的输出数据则是Byte类型。也就是说DataOutputStream可以将源数据转换成Byte类型的数据,再输出来。
这样一来,就可以将DataOutputStream与FileOutputStream链接起来,程序就可以将原始数据类型和String类型的源数据写入这个链接好的双重管道里面,达到将结构化数据写到磁盘文件里面的目的,如下图所示。
因此,Java I/O库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目标就是得到自己想要的数据类型的流对象。
由流管道的装饰模式也可以得出,凡是具有过滤和管道特征的应用都属于装饰器模式,例如Java EE中的Filter过滤器、UNIX中的管道符等,都属于装饰模式。装饰模式在软件的应用中随处可见。
Java中的应用—Sitemesh装饰器
在开发Web及J2EE应用时,Web页面可能由不同的人所开发,因此开发出来的界面通常会千奇百怪。随着项目进一步的开发,统一界面风格的紧迫性会逐渐浮现出来。
为此,Sitemesh框架出现了。该框架采用了装饰器模式,它通过创建一个包装对象,也就是装饰器来包裹真实的对象。尽管它是由Java语言来实现的,但它能与其他Web应用很好集成。
它为每一个请求的页面进行修饰,附加上其他的内容后再返回给客户端,其实现思路如图所示。
[size=medium][img]http://www.iteye.com/upload/picture/pic/100241/f7edb033-a0b8-349a-aca1-f7876746aa84-thumb.bmp?1318079932[/img][/size]
假设用户请求的是body.jsp页面,则Sitemesh会为body.jsp分别添加头部文件header.jsp、菜单栏menu.jsp、底部文件foot.jsp,然后再返回给客户端。这种方式的优点是:
Ø 开发页面body.jsp时,不需要考虑站点布局,因此也不用新建框架页面,JSP页面的数量是实际请求的页面数量。
Ø 要修改站点的布局,只需要修改Sitemesh的装饰配置即可。
显然,Sitemesh的方式更符合人性化的开发方式。这正是由于它使用了装饰器模式,使得我们的开发只需要关注局部,而不用关注待装饰的内容。
Decorator装饰器,顾名思义,就是动态地给一个对象添加一些额外的职责,就好比为房子进行装修一样。因此,装饰器模式具有如下的特征:
Ø 它必须具有一个装饰的对象。
Ø 它必须拥有与被装饰对象相同的接口。
Ø 它可以给被装饰对象添加额外的功能。
用一句话总结就是:保持接口,增强性能。
装饰器通过包装一个装饰对象来扩展其功能,而又不改变其接口,这实际上是基于对象的适配器模式的一种变种。它与对象的适配器模式的异同点如下。
Ø 相同点:都拥有一个目标对象。
Ø 不同点:适配器模式需要实现另外一个接口,而装饰器模式必须实现该对象的接口。
换句话说,对象的适配器模式是把一个对象适配成了另一个对象,而装饰器模式将丰富目标对象的功能但不改变它的接口。如下图所示,对象的适配器模式将一个圆形变成了正方形,而装饰器模式则不改变它的圆形形状,但改变了它的外观颜色。
装饰器模式的模型图如下图所示,共包括如下3个元素。
Ø 接口类Sourcable:定义了目标对象的接口。
Ø 源类Source:是接口Sourcable的一个实现。
Ø 装饰器类:它用来对Source对象进行装饰,但又必须实现Sourcable接口,以不改变Source对象的接口,根据装饰作用的不同,可以拥有多个装饰器类。
为了演示多次装饰的效果,我们这里分别创建了3个装饰器,第一个装饰器Decorator1装饰Source对象,第二个装饰器Decorator2装饰被第一个装饰器装饰后的对象,第三个装饰器Decorator2装饰被第二个装饰器装饰后的对象。
下面来看具体的实现。
(1)Sourcable类定义了一个接口函数operation()。
public interface Sourcable {
public void operation();
}
(2)Source是Sourcable的一个实现,其函数operation()负责往控制台输出一个字符串:原始类的方法。
public class Source implements Sourcable {
public void operation() {
System.out.println("原始类的方法");
}
}
(3)装饰器类Decorator1采用了典型的对象适配器模式,它首先拥有一个Sourcable对象 source,该对象通过构造函数进行初始化。然后它实现了Sourcable.java接口,以期保持与source同样的接口,并在重写的 operation()函数中调用source的operation()函数,在调用前后可以实现自己的输出,这就是装饰器所扩展的功能。
public class Decorator1 implements Sourcable {
private Sourcable source;
public Decorator1(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第1个装饰器装饰前");
source.operation();
System.out.println("第1个装饰器装饰后");
}
}
(4)装饰器类Decorator2是另一个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。
public class Decorator2 implements Sourcable {
private Sourcable source;
public Decorator2(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第2个装饰器装饰前");
source.operation();
System.out.println("第2个装饰器装饰后");
}
}
(5)装饰器类Decorator3是第三个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。
public class Decorator3 implements Sourcable {
private Sourcable source;
public Decorator3(Sourcable source) {
super();
this.source = source;
}
public void operation() {
System.out.println("第3个装饰器装饰前");
source.operation();
System.out.println("第3个装饰器装饰后");
}
}
这时,我们就可以像使用对象的适配器模式一样来使用这些装饰器,使用不同的装饰器就可以达到不同的装饰效果。例如,首先需要创建一个源类对象source,然后根据将对象使用Decorator1来装饰,并以此使用Decorator2、 Decorator3进行装饰,装饰后的对象同样具有与source同样的接口。
public class DecoratorTest {
public static void main(String[] args) {
// 创建源类对象
Sourcable source = new Source();
// 装饰类对象
Sourcable obj =
new Decorator1(
new Decorator2(
new Decorator3(source)
)
);
// 调用目标类的方法
obj.operation();
}
}
运行该程序的输出如下:
第1个装饰器装饰前
第2个装饰器装饰前
第3个装饰器装饰前
原始类的方法
第3个装饰器装饰后
第2个装饰器装饰后
第1个装饰器装饰后
从输出的结果可以看出,原始类对象source依次被Decorator1、Decorator2、Decorator3进行了装饰。
12.2.2 何时使用装饰器模式
由此可见,装饰模式提供了即插即用的方法,在运行期间决定何时增加何种功能。装饰模式与继承关系的目的都是要 扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式让系统动态地决定“贴上”一个需要的“装饰”,或者除掉一个不需要的装饰。而继承则不 同,继承关系是静态的,它在系统运行前就决定了。可以通过使用不同的具体修饰类及这些装饰类的排列组合,设计可以创造更多不同行为的组合。虽然比继承要灵 活,这意味着它比继承更容易出错。
因此,装饰模式一般在以下情况使用:
Ø 需要扩展一个类的功能,或给一个类增加附加责任。
Ø 需要动态地给一个对象增加功能,这些功能可以动态撤销。
Ø 需要增加由一些基本的排列组合产生大量的功能,从而使继承关系变得不现实。
但也需要注意装饰模式的缺点:由于使用装饰模式可以比使用继承关系需要较少的目的类,但是在另一方面,使用装饰模式会产生比使用继承方式更多的对象。这在使用时进行错误查询变得更困难了,特别是这些对象看上去都很像。
Java中的应用—I/O输入/输出流管道的装饰器模式
Java的I/O库提供了一个称做链接(Chaining)的机制,可以将一个流处理器与另一个流处理器首尾相接,以其中之一的输出为输入,形成一个流管道的链接。
例如,DataInputStream流处理器可以把FileInputStream流对象的输出当做输入,将Byte类型的数据转换成Java的原始类型和String类型的数据,如下图所示。
类似地,向一个文件写入Byte类型的数据不是一个简单的过程。一个程序需要向一个文件里写入的数据往往都是 结构化的,而Byte类型则是原始类型。因此在写的时候必须经过转换。DataOutputStream流处理器可以接收原始数据类型和String数据 类型,而这个流处理器的输出数据则是Byte类型。也就是说DataOutputStream可以将源数据转换成Byte类型的数据,再输出来。
这样一来,就可以将DataOutputStream与FileOutputStream链接起来,程序就可以将原始数据类型和String类型的源数据写入这个链接好的双重管道里面,达到将结构化数据写到磁盘文件里面的目的,如下图所示。
因此,Java I/O库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目标就是得到自己想要的数据类型的流对象。
由流管道的装饰模式也可以得出,凡是具有过滤和管道特征的应用都属于装饰器模式,例如Java EE中的Filter过滤器、UNIX中的管道符等,都属于装饰模式。装饰模式在软件的应用中随处可见。
Java中的应用—Sitemesh装饰器
在开发Web及J2EE应用时,Web页面可能由不同的人所开发,因此开发出来的界面通常会千奇百怪。随着项目进一步的开发,统一界面风格的紧迫性会逐渐浮现出来。
为此,Sitemesh框架出现了。该框架采用了装饰器模式,它通过创建一个包装对象,也就是装饰器来包裹真实的对象。尽管它是由Java语言来实现的,但它能与其他Web应用很好集成。
它为每一个请求的页面进行修饰,附加上其他的内容后再返回给客户端,其实现思路如图所示。
[size=medium][img]http://www.iteye.com/upload/picture/pic/100241/f7edb033-a0b8-349a-aca1-f7876746aa84-thumb.bmp?1318079932[/img][/size]
假设用户请求的是body.jsp页面,则Sitemesh会为body.jsp分别添加头部文件header.jsp、菜单栏menu.jsp、底部文件foot.jsp,然后再返回给客户端。这种方式的优点是:
Ø 开发页面body.jsp时,不需要考虑站点布局,因此也不用新建框架页面,JSP页面的数量是实际请求的页面数量。
Ø 要修改站点的布局,只需要修改Sitemesh的装饰配置即可。
显然,Sitemesh的方式更符合人性化的开发方式。这正是由于它使用了装饰器模式,使得我们的开发只需要关注局部,而不用关注待装饰的内容。