什么是装饰模式?
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
结构
- 抽象构件(Component)角色 :定义一个抽象接口以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色 :实现抽象构件,通过装饰角色为其添加一些职责。
- 抽象装饰(Decorator)角色 : 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
- 具体装饰(ConcreteDecorator)角色 :实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
UML类图
代码理解
// 定义饮品的抽象类
public abstract class Beverage {
private String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public abstract double cost(); // 获取饮品价格
}
// 创建具体饮品类
public class Coffee extends Beverage {
public Coffee() {
// 设置饮品描述
super.description = "Coffee";
}
@Override
public double cost() {
return 5.00; // Coffee的基础价格
}
}
// 创建装饰者基类,实现Beverage的装饰器
public abstract class CondimentDecorator extends Beverage {
protected Beverage beverage; // 持有一个Beverage对象
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public abstract String getDescription(); // 装饰者类必须实现此方法
}
// 添加牛奶的装饰者
public class MilkDecorator extends CondimentDecorator {
public MilkDecorator(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk"; // 添加牛奶描述
}
@Override
public double cost() {
return beverage.cost() + 1.50; // 牛奶的附加价格
}
}
// 添加糖的装饰者
public class SugarDecorator extends CondimentDecorator {
public SugarDecorator(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Sugar"; // 添加糖描述
}
@Override
public double cost() {
return beverage.cost() + 0.50; // 糖的附加价格
}
}
// 客户端代码
public class CoffeeShop {
public static void main(String[] args) {
// 创建一个基本的Coffee
Beverage beverage = new Coffee();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 装饰Coffee对象,添加牛奶
beverage = new MilkDecorator(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 再次装饰,添加糖
beverage = new SugarDecorator(beverage);
System.out.println(beverage.getDescription() + " $" + beverage.cost());
}
}
使用场景
- 当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况主要有两类:
-
- 第一类是系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长;
- 第二类是因为类定义不能继承(如final类)
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 当对象的功能要求可以动态地添加,也可以再动态地撤销时。
案例分析
JDK源码-IO流
BufferedWriter 通过“包装”另一个流对象(比如 FileWriter),为它增加额外的功能(例如缓冲区的处理)。我们来看看 BufferedWriter 的内部结构,以及它如何实现装饰者模式。
BufferedWriter 的结构分析
BufferedWriter 是通过装饰者模式扩展 Writer 类的功能,它增强了写入操作的性能,减少了对底层流的操作次数。BufferedWriter 本身并没有实现具体的写入逻辑,而是将写入操作委托给它包装的 Writer 对象。下面是 BufferedWriter 的部分代码结构示例。
1. BufferedWriter 的实现
public class BufferedWriter extends Writer {
private final Writer out; // 被装饰的Writer对象
private final char[] buffer; // 内部缓冲区
private int position; // 当前缓冲区的位置
// 构造方法,接收一个Writer对象作为参数
public BufferedWriter(Writer out) {
this.out = out;
this.buffer = new char[8192]; // 默认缓冲区大小
this.position = 0;
}
@Override
public void write(int c) throws IOException {
if (position >= buffer.length) {
flushBuffer(); // 如果缓冲区已满,则刷新
}
buffer[position++] = (char) c; // 将字符写入缓冲区
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
// 判断缓冲区大小并进行填充
if (len >= buffer.length) {
flushBuffer();
out.write(cbuf, off, len);
return;
}
if (position + len > buffer.length) {
flushBuffer();
}
System.arraycopy(cbuf, off, buffer, position, len);
position += len;
}
@Override
public void flush() throws IOException {
flushBuffer();
out.flush();
}
// 内部刷新缓冲区
private void flushBuffer() throws IOException {
if (position > 0) {
out.write(buffer, 0, position);
position = 0;
}
}
@Override
public void close() throws IOException {
flush();
out.close();
}
}
2. FileWriter 的结构
BufferedWriter 的构造方法中接收了一个 Writer 对象作为参数,我们看一下 FileWriter 的实现,它是一个继承自 Writer 的具体类。
public class FileWriter extends Writer {
private final FileOutputStream out;
public FileWriter(String fileName) throws IOException {
this.out = new FileOutputStream(fileName);
}
@Override
public void write(int c) throws IOException {
out.write(c);
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
// 将字符数组转换为字节数组进行写入
out.write(new String(cbuf, off, len).getBytes());
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
}
3. 总结装饰者模式的应用
从上面的代码可以看出,BufferedWriter 通过装饰者模式实现了它的功能。BufferedWriter 本身并不提供实际的写入操作,它将所有的写入操作委托给了它所包装的 Writer(比如 FileWriter)。同时,BufferedWriter 通过缓存字符并在缓冲区填满时进行一次性写入,提升了性能。