装饰模式(DECORATOR模式),又称包装器Wrapper,属于对象结构型模式,用于动态地给一个对象添加一些额外的职责。就增加功能而言,Decorator模式比生成子类更为灵活。它通过使用对象之间的关联关系取代类之间的继承关系,引入的装饰类既可以调用被装饰类的方法,还可以添加新的业务逻辑,以达到扩充被装饰类的功能。装饰模式和适配器模式虽然都称为包装器,但两者还是不同的。装饰模式只是在原有对象的基础上增加新的职责,而不改变对外提供的接口。适配器模式用于解决接口之间的不兼容问题,必然会改变对外提供的接口。
一、使用场景
1、在不影响其他对象的情况下,以动态,透明的方式给单个对象添加职责。装饰模式采用对象组合而非类继承方式扩展功能,所以对于需要扩充的对象可以动态添加,切换,删除相应的职责而不影响其他对象。
2、处理那些可以撤销的职责,对于需要某项职责的对象只需关联相应的装饰对象即可。
3、当不能采用继承的方式对系统进行扩展或者采用继承不利于系统扩展和维护时。一种情况是系统中存在大量独立的扩展,为支持每一种扩展或者扩展之间的组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏或类不能被继承,比如java中类定义为final类。
二、UML图
三、Java实现
package study.patterns.decorator;
/**
* 装饰模式:降低系统的耦合度,可以动态增加或删除对象的职责,
* 并使得需要装饰的具体构件类和具体装饰类可以独立变化,
* 以便增加新的具体构件类和具体装饰类。
* 如果需要在原有系统中增加一个新的具体构件类或者新的具体装饰类,
* 无须修改现有类库代码,只需将它们分别作为抽象构件类或者抽象装饰类的子类即可.
* @author qbg
*/
public class DecoratorPattern {
public static void main(String[] args) {
String test = "test123";
//使用默认装饰类和不使用装饰类效果一样
IStream dStream = new StreamDecorator(new MemoryStream());
dStream.write(test);
String r0 = dStream.read();
System.out.println("======使用默认装饰类:"+r0);
//使用压缩装饰类
IStream cStream = new CompressingStream(new MemoryStream());
cStream.write(test);
String r1 = cStream.read();
System.out.println("======使用压缩装饰类:"+r1);
//使用ASCII装饰类
IStream aStream = new ASCII7Strem(new FileStream());
aStream.write(test);
String r2 = aStream.read();
System.out.println("======使用ASCII装饰类:"+r2);
//先使用ASCII装饰类进行编码转换,然后使用压缩装饰类进行压缩
IStream caStream = new ASCII7Strem(new CompressingStream(new FileStream()));
caStream.write(test);
String r3 = caStream.read();
System.out.println("======使用ASCII和压缩装饰类:"+r3);
}
}
/**
* I/O流接口
*/
interface IStream{
/**
* 读取流
*/
public String read();
/**
* 写入流
*/
public void write(String str);
}
/**
* 流介质为内存型
*/
class MemoryStream implements IStream{
private String stream;
@Override
public String read() {
System.out.println("从内存中读取流...");
return stream;
}
@Override
public void write(String stream) {
System.out.println("往内存中写入流...");
this.stream = stream;
}
}
/**
* 流介质为文件系统
*/
class FileStream implements IStream{
private String stream;
@Override
public String read() {
System.out.println("从文件中读取流...");
return stream;
}
@Override
public void write(String stream) {
System.out.println("往文件中写入流...");
this.stream = stream;
}
}
/**
* 流装饰默认实现类
*/
class StreamDecorator implements IStream{
private IStream stream;
public StreamDecorator(IStream stream){
this.stream = stream;
}
@Override
public String read() {
return this.stream.read();
}
@Override
public void write(String stream) {
this.stream.write(stream);
}
}
/**
* 将流数据转换为7位ASCII码
*/
class ASCII7Strem extends StreamDecorator{
public ASCII7Strem(IStream stream) {
super(stream);
}
@Override
public void write(String stream) {
super.write(toAscii7(stream));
}
/*
* 额外职责
*/
private String toAscii7(String stream){
System.out.println("将数据转换为7位ASCII码");
return "ASCII["+stream+"]";
}
}
/**
* 将流数据压缩
*/
class CompressingStream extends StreamDecorator{
public CompressingStream(IStream stream) {
super(stream);
}
@Override
public void write(String stream) {
super.write(compress(stream));
}
/*
* 额外职责
*/
private String compress(String stream){
System.out.println("压缩数据");
return "压缩["+stream+"]";
}
}
运行结果:
往内存中写入流...
从内存中读取流...
======使用默认装饰类:test123
压缩数据
往内存中写入流...
从内存中读取流...
======使用压缩装饰类:压缩[test123]
将数据转换为7位ASCII码
往文件中写入流...
从文件中读取流...
======使用ASCII装饰类:ASCII[test123]
将数据转换为7位ASCII码
压缩数据
往文件中写入流...
从文件中读取流...
======使用ASCII和压缩装饰类:压缩[ASCII[test123]]
四、模式优缺点
优点:
1、 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。
2、可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
3、可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。
4、具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库代码无须改变,符合“开闭原则”。
5、避免在层次结构高层的类有太多的特征。高层的类越简单,其可扩展性,灵活性越好。可以定义一个简单的类,然后使用Decorator类给它逐渐添加功能。
缺点:
1、 使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量小对象的产生势必会占用更多的系统资源,在一定程序上影响程序的性能,也增大了学习系统的复杂度。
2、装饰模式提供了一种比继承更加灵活机动的解决方案,但同时也意味着比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为繁琐。
3、Decorator与它的Component不一样,Decorator只是透明的包装,所以客户端使用时不应该依赖Decorator。