系列文章目录
肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
文章目录
- 系列文章目录
- 前言
- 一、什么是装饰器模式
- 二、装饰器模式中的角色
- 三、举个栗子
- 四、在JDK中的应用
- 写在最后
前言
我们知道设计模式可以分为三大类:创建型模式、结构型模式、行为型模式。上一节我们分析了结构型模式中的适配器模式,这一节我们继续分析另外一种结构型模式——装饰器模式
一、什么是装饰器模式
装饰器模式(Decorator Pattern),它允许在不改变已有对象的基础上动态地扩展其功能。通过将对象包装在装饰器对象中,可以在运行时为对象添加额外的行为,就增加功能来说,装饰器模式比生成子类更灵活。
装饰器模式的关键是使用组合而非继承来实现功能的扩展。通过将对象包装在装饰器对象中,可以按需添加或修改对象的行为,而无需改变原始对象或其他代码。
二、装饰器模式中的角色
在装饰器模式中,有一个核心的接口或抽象类,代表要被装饰的对象。
然后,创建一个装饰器类,它实现了相同的接口或继承了相同的抽象类,并在构造函数中接受被装饰的对象作为参数。
装饰器类中包含一个与被装饰对象相同的成员变量,用于保存被装饰的对象。
在装饰器类中,可以调用被装饰对象的方法,并可以在方法调用前后添加额外的逻辑。
以下是装饰器模式的几个关键角色:
- Component(抽象组件):定义了被装饰对象的接口或抽象类。
- ConcreteComponent(具体组件):实现了组件接口或抽象类,是被装饰的对象。
- Decorator(装饰器):实现了组件接口或抽象类,并在构造函数中接受被装饰对象。装饰器可以调用被装饰对象的方法,并可以添加额外的功能。
- ConcreteDecorator(具体装饰器):扩展了装饰器类,实现了额外的功能。
三、举个栗子
说了这么多的概念,感觉懂了又好像没懂,那就必须举个栗子了,我在第一次了解装饰器模式的时候,第一时间就想到了玩一款RPG游戏,你可以为你的角色穿上装备,也可以不穿装备硬刚ヽ( ̄▽ ̄)ノ
首先创建一个基础的角色类(Character)作为抽象组件,并定义一个角色的基本操作:
public interface Character {
void attack();
}
然后我们再建一个具体的角色类,比如新建一个战士(Warrior),实现角色接口:
public class Warrior implements Character {
@Override
public void attack() {
System.out.println("赤手空拳的攻击!");
}
}
现在我们准备给我们的战士加点装备,比如使用武器或者戴上防具,这时就可以使用装饰器模式了。
首先先定义一个装饰器抽象类(Decorator),也实现了角色接口:
public abstract class Decorator implements Character {
protected Character character;
public Decorator(Character character) {
this.character = character;
}
@Override
public void attack() {
character.attack();
}
}
然后,创建具体的装饰器类来实现附加功能。
比如,创建一个武器装饰器类(WeaponDecorator):
public class WeaponDecorator extends Decorator {
public WeaponDecorator(Character character) {
super(character);
}
@Override
public void attack() {
super.attack();
addWeapon();
}
private void addWeapon() {
System.out.println("装上利维坦之斧削你!");
}
}
再创建一个防具装饰器类(ArmorDecorator):守护者之盾
public class ArmorDecorator extends Decorator {
public ArmorDecorator(Character character) {
super(character);
}
@Override
public void attack() {
super.attack();
addArmor();
}
private void addArmor() {
System.out.println("戴上守护者之盾我防!");
}
}
测试一下
public class DecoratorClient {
public static void main(String[] args) {
Character warrior = new Warrior();
warrior.attack(); // 输出:赤手空拳的攻击!
Character warriorWithWeapon = new WeaponDecorator(warrior);
warriorWithWeapon.attack(); // 输出:赤手空拳的攻击!装上利维坦之斧削你!
Character warriorWithWeaponAndArmor = new ArmorDecorator(warriorWithWeapon);
warriorWithWeaponAndArmor.attack(); // 输出:赤手空拳的攻击!装上利维坦之斧削你!戴上守护者之盾我防!
}
}
通过装饰器模式,我们可以动态地为玩具对象添加不同的装饰器,以实现不同的附加功能,而无需修改原有的玩具类或客户端代码。这种方式可以灵活地组合和扩展功能,同时保持代码的清晰
四、在JDK中的应用
JDK中使用装饰器模式的地方,一个典型的例子就是Java I/O库中使用 BufferedInputStream 和 BufferedOutputStream 类对输入流和输出流进行包装和装饰。
简单示例:
public class DecoratorClient {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("input.txt");
FileOutputStream fileOutputStream = new FileOutputStream("output.txt");
// 使用装饰器对输入流进行包装和装饰
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
// 使用装饰器对输出流进行包装和装饰
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
// 读取和写入数据
int data;
while ((data = bufferedInputStream.read()) != -1) {
bufferedOutputStream.write(data);
}
// 关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在示例当中,我们使用了 FileInputStream 和 FileOutputStream 来创建原始的输入流和输出流。然后,我们分别使用 BufferedInputStream 和 BufferedOutputStream 对这些原始流进行装饰。
以BufferedInputStream为例,看一下源码摘要(省略了一些方法):
public class BufferedInputStream extends FilterInputStream {
// ...
public BufferedInputStream(InputStream in) {
super(in);
// ...
}
// ...
}
在上述代码中,BufferedInputStream 继承了 FilterInputStream 类,FilterInputStream 本身是一个抽象类,也是装饰器类。FilterInputStream 继承了 InputStream 抽象类,从而与原始的输入流进行连接。
构造函数 BufferedInputStream(InputStream in) 接受一个原始的输入流对象作为参数,并通过调用 super(in) 将其传递给父类 FilterInputStream 的构造函数。
通过这样的继承关系和构造函数的调用,BufferedInputStream 实际上是一个装饰器类,它可以包装和装饰任何实现了 InputStream 接口的输入流对象。
BufferedInputStream 通过添加缓冲功能来装饰输入流。它维护了一个内部缓冲区,通过调用原始输入流的 read() 方法将数据从原始流读入到缓冲区中,并通过提供额外的方法(如 read(byte[] b, int off, int len))来从缓冲区中读取数据,以提高读取的性能。
写在最后
装饰器模式的优点:
- 可以在不修改已有代码的情况下扩展对象的功能。
- 允许多个装饰器按照一定顺序进行组合,以实现复杂的功能组合。
- 装饰器与被装饰对象之间是松耦合的,可以独立地进行扩展和修改。
装饰器模式的缺点:
- 会导致类的增多,增加了代码复杂性,过度使用装饰器模式也可能使代码难以理解和维护。