Java设计模式之结构型—装饰器模式

发布于:2025-09-10 ⋅ 阅读:(14) ⋅ 点赞:(0)

装饰器模式(Decorator Pattern):不动原始类,动态给对象加功能
比继承更灵活,避免“子类爆炸”,是 Java IO 流、集合包装、Spring AOP 的底层思想。

下面用“一杯奶茶”+“源码”双重例子

生活例子:奶茶加料

基础奶茶 5¥
  +珍珠  +2¥
  +椰果  +1¥
  +奶盖  +3¥

用继承要写 8 个子类;用装饰器,运行时任意组合

UML 与角色

Component(接口)         Beverage
 └─ ConcreteComponent(基础实现) MilkyTea

Decorator(抽象装饰器)     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 装饰 FileInputStreamBufferedReader 再装饰前者,逐层加功能。

集合

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)**仍是分水岭:

  1. 目的不同

  • 装饰器:迟早要加新功能(哪怕现在没加,框架预留了“套娃”能力)。

  • 代理:要控制、屏蔽、扩展流程(权限、远程、懒加载、事务等)。

  1. 设计演进方向不同

  • 装饰器 → 支持多层嵌套、功能叠加(BufferedInputStream→LineNumberInputStream→...)。

  • 代理 → 通常固定一层,再套就没有“守门”意义了。

  1. 客户端感知

  • 装饰器:客户端主动套娃new Decorator(real),知道在叠加功能。

  • 代理:客户端只想拿到原接口即可,代理是透明的(Spring AOP、RPC stub)。

  1. 是否一定调用原方法

  • 装饰器:会调用,否则“增强”就断了。

  • 代理:可调用、可不调用(权限失败直接抛异常,不转给原对象)。

因此,单层且行为不变的代码确实像,但:

  • 未来是否叠加功能→装饰器

  • 是否要控制/隐藏原对象→代理

一句话:
结构像双胞胎,意图两条路;一层不增强,代理来守门;以后还想套,装饰留后手。


网站公告

今日签到

点亮在社区的每一天
去签到