装饰器模式(Decorator)

本文详细解释了装饰器模式的核心思想及其在软件开发中的应用,包括装饰器模式的特点、工作原理、实现过程以及Java中的实例应用,如I/O输入/输出流管道的装饰器模式和Sitemesh框架的装饰器模式。文章还探讨了装饰器模式的使用场景、优点和缺点,旨在帮助开发者更好地理解和运用这一设计模式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装饰器模式的核心思想
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的方式更符合人性化的开发方式。这正是由于它使用了装饰器模式,使得我们的开发只需要关注局部,而不用关注待装饰的内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值