Java设计模式之结构型—装饰器模式
装饰器模式(Decorator Pattern):不动原始类,动态给对象加功能,
比继承更灵活,避免“子类爆炸”,是 Java IO 流、集合包装、Spring AOP 的底层思想。
下面用“一杯奶茶”+“源码”双重例子
生活例子:奶茶加料
基础奶茶 5¥+珍珠 +2¥+椰果 +1¥+奶盖 +3¥
用继承要写 8 个子类;用装饰器,运行时任意组合。
UML 与角色
Component(接口) Beverage└─ ConcreteComponent(基础实现) MilkyTeaDecorator(抽象装饰器) ToppingDecorator├─ PearlTopping├─ CoconutTopping└─ CreamTopping
代码实现(奶茶)
// 1. 组件接口
interface Beverage {String desc();int cost();
}// 2. 基础实现
class MilkyTea implements Beverage {public String desc() { return "奶茶"; }public int cost() { return 5; }
}// 3. 装饰器骨架
abstract class ToppingDecorator implements Beverage {protected Beverage beverage; // 持有被装饰对象ToppingDecorator(Beverage b) { this.beverage = b; }
}// 4. 具体装饰(可加任意多个)
class PearlTopping extends ToppingDecorator {PearlTopping(Beverage b) { super(b); }public String desc() { return beverage.desc() + " + 珍珠"; }public int cost() { return beverage.cost() + 2; }
}class CoconutTopping extends ToppingDecorator {CoconutTopping(Beverage b) { super(b); }public String desc() { return beverage.desc() + " + 椰果"; }public int cost() { return beverage.cost() + 1; }
}
客户端:任意组合
Beverage b = new MilkyTea(); // 5¥
b = new PearlTopping(b); // +2¥
b = new CoconutTopping(b); // +1¥
System.out.println(b.desc() + " = " + b.cost()); // 奶茶 + 珍珠 + 椰果 = 8¥
运行期加料,一行代码一个功能,无需继承。
Java 标准库随处可见
IO 流
new BufferedReader(new InputStreamReader(new FileInputStream("a.txt")));
InputStreamReader
装饰 FileInputStream
,BufferedReader
再装饰前者,逐层加功能。
集合
List<String> list = Collections.synchronizedList(new ArrayList<>());
synchronizedList
是装饰器,给 ArrayList
加同步。
优缺点一句话
优点 | 缺点 |
---|---|
比继承灵活,运行期任意组合 | 会产生很多小类;调试略绕 |
背板口诀
“装饰器,套娃皮,组合代替继承;Java IO 和同步集,层层包装加功能。”
装饰器模式和代理模式区别
装饰器 = 功能增强(加法)
代理 = 控制访问(守门)
维度 | 装饰器 Decorator | 代理 Proxy |
---|---|---|
目的 | 给对象 添加/组合 新职责 | 对对象 控制/约束 访问 |
接口 | 必须实现 同一接口 | 通常实现 同一接口 |
是否透明 | 客户端察觉不到被装饰 | 客户端知道代理存在 |
是否持有原对象 | 有,并递归调用原方法 | 有,但可拦截/不调用原方法 |
生命周期 | 运行期动态组合 | 编译/运行期固定代理 |
典型代码 | new BufferedReader(new FileReader()) | UserService proxy = new UserServiceProxy(real) |
JDK 例子 | Java IO 流、Collections 同步包装 | RMI 远程代理、Spring AOP |
生活例子对比
装饰器:给枪加消音器、瞄准镜
Gun gun = new AK47();
gun = new Silencer(gun); // 增强:消音
gun = new Scope(gun); // 再增强:瞄准
gun.shoot(); // 功能累加
代理:给枪加指纹锁(控制能否射击)
Gun real = new AK47();
Gun proxy = new FingerprintGunProxy(real);
proxy.shoot(); // 代理先校验指纹,再/或不再调 real.shoot()
代码级差异(同一接口 Gun)
// 装饰器:递归增强
class Silencer implements Gun {private Gun gun;public void shoot() {System.out.print("静音-");gun.shoot(); // 一定调原功能}
}// 代理:控制访问
class FingerprintGunProxy implements Gun {private Gun gun;public void shoot() {if (verifyFinger()) gun.shoot(); // 可拦截else System.out.println("拒绝射击");}
}
“装饰加功能,代理做门卫;一个套娃增强,一个权限控制。”
装饰器模式只有一层的情况下和代理模式几乎一摸一样?
是的,只有一层装饰且不改行为时,代码结构看起来跟代理几乎双胞胎:都实现同一接口、都持有原对象、都转发调用。
但**意图(purpose)**仍是分水岭:
-
目的不同
-
装饰器:迟早要加新功能(哪怕现在没加,框架预留了“套娃”能力)。
-
代理:要控制、屏蔽、扩展流程(权限、远程、懒加载、事务等)。
-
设计演进方向不同
-
装饰器 → 支持多层嵌套、功能叠加(BufferedInputStream→LineNumberInputStream→...)。
-
代理 → 通常固定一层,再套就没有“守门”意义了。
-
客户端感知
-
装饰器:客户端主动套娃
new Decorator(real)
,知道在叠加功能。 -
代理:客户端只想拿到原接口即可,代理是透明的(Spring AOP、RPC stub)。
-
是否一定调用原方法
-
装饰器:会调用,否则“增强”就断了。
-
代理:可调用、可不调用(权限失败直接抛异常,不转给原对象)。
因此,单层且行为不变的代码确实像,但:
-
看未来是否叠加功能→装饰器
-
看是否要控制/隐藏原对象→代理
一句话:
“结构像双胞胎,意图两条路;一层不增强,代理来守门;以后还想套,装饰留后手。”