装饰器模式
定义
装饰模式(decorator pattern)的原始定义是:动态地给一个对象添加一些额外的职责;就扩展功能而言,装饰者模式提供了一种比使用子类更加灵活的替代方案。
假设现在有一块蛋糕,如果只有涂上奶油那这个蛋糕就是普通的奶油蛋糕,这时如果我们添加上一些蓝莓,那这个蛋糕就是蓝莓蛋糕,如果我们再拿一块黑巧克力,然后写上姓名、插上代表年龄的蜡烛,这就是变成了一块生日蛋糕。
在软件设计中,装饰者模式是一种用于替代继承关系的技术,它通过一种无需定义子类的方式给对象动态的添加职责,适用对象之间的关联关系取代类之间的继承关系。
模式原理
核心角色
-
Component(抽象构件角色):它是 具体构件和抽象装饰类的共同父类。
-
ConcreteComponent(具体构件角色):抽象构件角色的子类,用于定义具体的构件对象。
-
Decorator(抽象装饰角色):它也是抽象构件角色的子类,用于给具体的构件角色增加职责。
-
ConcreteDecorator(具体装饰角色):它是抽象装饰类的子类,它负责向构件角色添加新的功能

代码实例
/**
* 抽象构建类:定义了具体构件类中
*/
public abstract class Component {
// 抽象方法
public void operation(){
System.out.println("基础功能的实现(一些复杂功能通过装饰类进行扩展)");
};
}
/**
* 具体构件类(被装饰类)
*/
public class ConcreteComponent extends Component{
@Override
public void operation() {
System.out.println(" = ");
}
}
/**
* 抽象装饰类(装饰者模式的核心)
*/
public class Decorator extends Component{
// 维持一个对抽象构件对象的引用
private Component component;
// 通过构造注入一个抽象构件类型的对象
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
// 调用原有的业务方法,并没有真正的进行装饰,而是提供了一个统一的接口,将装饰的过程交给子类完成
component.operation();
}
}
public class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void operation() {
// 完成对被装饰类的装饰
super.operation();// 调用原有的业务方法
add();
}
// 新增业务方法
public void add (){
// ...
System.out.println("调用新增的方法");
}
}
应用案例一
以一个文件读写器程序为例,演示装饰者模式的使用,下面是 UML 类图。

- DataLoader: 抽象的文件读取接口 DataLoader
/**
* 抽象的文件读取接口
* @author 21129
*/
public interface DataLoader {
String read();
void write(String data);
}
- BaseFileDataLoader: 具体的组件,需要重写 DataLoader 接口中的读写方法,实现了文件读取功能
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
/**
* 具体组件:抽象文件读取接口的实现类
* 需要 完成最基础的读写操作
* @author 21129
*/
public class BaseFileDataLoader implements DataLoader {
private String filePath;
public BaseFileDataLoader(String filePath) {
this.filePath = filePath;
}
@Override
public String read() {
try {
return FileUtils.readFileToString(new File(filePath),"utf-8");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void write(String data) {
try {
FileUtils.writeStringToFile(new File(filePath), data,"utf-8");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 抽象装饰者类,这里不进行扩展,在具体装饰者中进行扩展
/**
* 抽象装饰者类,这里不进行扩展,在具体装饰者中进行扩展,
* @author 21129
*/
public abstract class DataLoaderDecorator implements DataLoader{
private DataLoader dataLoader;
protected DataLoaderDecorator(DataLoader dataLoader) {
this.dataLoader = dataLoader;
}
@Override
public String read() {
return dataLoader.read();
}
@Override
public void write(String data) {
dataLoader.write(data);
}
}
- 具体装饰者类:比如对文件内容进行加密和解密
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;
/**
* 具体装饰者类:对文件内容进行加密和解密
*/
public class EncryptionDataDecorator extends DataLoaderDecorator{
protected EncryptionDataDecorator(DataLoader dataLoader) {
super(dataLoader);
}
@Override
public String read() {
return decode(super.read());
}
@Override
public void write(String data) {
// 加密 data ,然后进行存储
super.write(encode(data));
}
// 加密操作
public String encode(String data) {
Encoder encoder = Base64.getEncoder();
try {
byte[] bytes = data.getBytes("UTF-8");
String result = encoder.encodeToString(bytes);
return result;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
// 解密操作
public String decode(String data) {
Decoder decoder = Base64.getDecoder();
try {
String s = new String(decoder.decode(data),"utf-8");
return s;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
总结
优点
-
对于扩展一个对象的功能,装饰模式比继承更加灵活,不会导致类的个数急剧增加。
-
可以通过一种动态的方式来扩展一个对象的功能,通过配置文件可以在运行时选择不同的具体装饰类,从而实现不同的行为。
-
可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合可以创造出很多不同行为的组合,得到更加强大的对象。
-
具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,原有类库无需改变,符合开闭原则。
缺点
-
在使用装饰模式进行系统设计时将产生很多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值不同,大量的小对象的产生势必会占用更多的系统资源,在一定程度上影响系统的性能。
-
装饰器模式提供了一种比继承更加灵活、机动的解决方案,但也意味着比继承更加易于出错,排错也更加困难,对于多次装饰的对象,在调试寻找问题时可能需要逐级排查,较为繁琐。
适用场景
-
快速动态扩展和撤销一个类的功能场景。比如,有些场景下对 API 接口的安全性要求较高,那么就可以使用装饰模式对传输的字符串进行压缩或加密。如果安全性要求不高,则可以不使用。
-
不支持继承扩展类的场景。比如使用 final 关键字的类,或者在系统中存在大量通过继承产生的子类。