设计模式-装饰器模式

一、装饰器模式的核心思想

Decorator 装饰器,顾名思义,就是动态地给一个对象添加一些额外的职责,就好比为房子进行装修一样。因此,装饰器模式具有如下的特征:

  • 它必须具有一个装饰的对象。
  • 它必须拥有与被装饰对象相同的接口。
  • 它可以给被装饰对象添加额外的功能。

用一句话总结就是:保持接口,增强性能

装饰器通过包装一个装饰对象来扩展其功能,而又不改变其接口,这实际上是基于对象的适配器模式的一种变种。它与对象的适配器模式的异同点如下。

  • 相同点:都拥有一个目标对象。
  • 不同点:适配器模式需要实现另外一个接口,而装饰器模式必须实现该对象的接口。

换句话说,对象的适配器模式是把一个对象适配成了另一个对象,而装饰器模式将丰富目标对象的功能但不改变它的接口。如下图所示,对象的适配器模式将一个圆形变成了正方形,而装饰器模式则不改变它的圆形形状,但改变了它的外观颜色。
在这里插入图片描述
装饰器模式的模型图如下图所示,共包括如下3个元素。
在这里插入图片描述

  • 接口类Sourcable:定义了目标对象的接口。
  • 源类Source:是接口 Sourcable 的一个实现。
  • 装饰器类:它用来对 Source 对象进行装饰,但又必须实现 Sourcable 接口,以不改变 Source对象的接口,根据装饰作用的不同,可以拥有多个装饰器类。

为了演示多次装饰的效果,我们这里分别创建了3个装饰器,第一个装饰器Decorator1 装饰Source 对象,第二个装饰器 Decorator2 装饰被第一个装饰器装饰后的对象,第三个装饰器 Decorator2装饰被第二个装饰器装饰后的对象。

下面来看具体的实现。
(1) Sourcable 类的源代码如下程序所示,其定义了一个接口函数 operation()。

源接口 Sourcable.java

package structure.decorator;


/**
* @author Minggg
* 源接口
*/
public interface Sourcable {
	public void operation();
}

(2) Source.java是 Sourcable.java的一个实现,其函数 operation()负责往控制台输出一个字符串原始类的方法。其源代码如下程序所示。

源类 Source.java

package structure.decorator;

/**
* @author Minggg
* 源类
*/
public class Source implements Sourcable {

	public void operation(){
		System.out.println("原始类的方法");
	}
}

(3) 装饰器类 Decorator1.java采用了典型的对象适配器模式,它首先拥有一个 Sourcable 对象source,该对象通过构造函数进行初始化。然后它实现了Sourcable.java接口,以期保持与source 同样的接口,并在重写的 operation()函数中调用 source的 operation()函数,在调用前后可以实现自己的输出,这就是装饰器所扩展的功能。其源代码如下程序所示。

装饰器类Decorator1.java

package structure.decorator;
/**
* @author Minggg
* 装饰器类
*/
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.java是另一个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。其源代码如下程序所示。

装饰器类 Decorator2.java

package structure.decorator;
/**
* @author Minggg
* 装饰器类
*/
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) 装饰器类 Decorator1.java是第三个装饰器,不同的是它装饰的内容不一样,即输出了不同的字符串。其源代码如下程序所示。

装饰器类Decorator3.iava

package structure.decorator;
/**
* @author Minggg
* 装饰器类
*/
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同样的接口。

测试类DecoratorTest.iava

package structure.decorator;

/**
* @author Minggg
* 按照目标接口来创建实例,并调用该接口的各个实现函数
*/
public class DecoratorTest {

	public statie 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 进行了装饰。

二、何时使用装饰器模式

由此可见,装饰模式提供了即插即用的方法,在运行期间决定何时增加何种功能。装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式让系统动态地决定“贴上”一个需要的“装饰”,或者除掉一个不需要的装饰。而继承则不同,继承关系是静态的,它在系统运行前就决定了。可以通过使用不同的具体修饰类及这些装饰类的排列组合,设计可以创造更多不同行为的组合。虽然比继承要灵活,这意味着它比继承更容易出错。

因此,装饰模式一般在以下情况使用:

  • 需要扩展一个类的功能,或给一个类增加附加责任。
  • 需要动态地给一个对象增加功能,这些功能可以动态撤销。
  • 需要增加由一些基本的排列组合产生大量的功能,从而使继承关系变得不现实。

但也需要注意装饰模式的缺点:由于使用装饰模式可以比使用继承关系需要较少的目的类,但是在另一方面,使用装饰模式会产生比使用继承方式更多的对象。这在使用时进行错误查询变得更困难了,特别是这些对象看上去都很像。

三、Java 中的应用–I/0 输入/输出流管道的装饰器模式

Java的 I/O库提供了一个称做链接(Chaining)的机制,可以将一个流处理器与另一个流处理器首尾相接,以其中之一的输出为输入,形成一个流管道的链接。

例如,DataInputStream 流处理器可以把 FileInputStream 流对象的输出当做输入,将 Byte 类型的数据转换成 Java 的原始类型和 String 类型的数据,如下图所示。
在这里插入图片描述
类似地,向一个文件写入 Byte 类型的数据不是一个简单的过程。一个程序需要向一个文件里写入的数据往往都是结构化的,而Byte类型则是原始类型。因此在写的时候必须经过转换。DataOutputStream 流处理器可以接收原始数据类型和 String数据类型,而这个流处理器的输出数据则是 Byte 类型。也就是说 DataOutputStream 可以将源数据转换成 Byte 类型的数据,再输出来。

这样一来,就可以将 DataOutputStream与FileOutputStream 链接起来,程序就可以将原始数据类型和 String 类型的源数据写入这个链接好的双重管道里面,达到将结构化数据写到磁盘文件里面的目的,如下图所示。
在这里插入图片描述
因此,Java I/O库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目标就是得到自己想要的数据类型的流对象。

由流管道的装饰模式也可以得出,凡是具有过滤和管道特征的应用都属于装饰器模式,例如 JavaEE 中的 Filter 过滤器、UNIX中的管道符等,都属于装饰模式。装饰模式在软件的应用中随处可见。

  • 39
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值