委托模式(Delegation Pattern)深度解析:基于 java-design-patterns 仓库的 Java 实战指南
委托模式(Delegation Pattern)深度解析:基于 java-design-patterns 仓库的 Java 实战指南
委托模式(Delegation Pattern)是 Java 面向对象设计中一种通过对象组合而非继承来实现行为复用的结构性技术:对象对外表达某种行为,却将实现该行为的责任转交给关联的辅助对象。本指南以 java-design-patterns 开源仓库中的 delegation 模块为蓝本,从模式意图、类图与时序图、源码级实现、测试验证、适用场景到收益权衡进行全链路剖析,读完即可在真实项目中复现这一解耦设计。
模式概述:目的与别名
在 delegation 模块文档(英文版)与中文翻译文档中,委托模式被定义为:
一种让对象将某种行为向外部表达,但实际上将实现该行为的责任委托给关联对象的技术。
英文文档给出了更精炼的意图表述:允许一个对象将某项任务的执行责任委托给另一个辅助对象(helper object)。中文文档同时列出其别名“代理模式”(Proxy Pattern 的另一种称谓),英文文档则补充了 "Helper" 与 "Surrogate" 两个别名。
从 Wikipedia 的经典定义来看:在面向对象编程中,委托指在一个对象(接收者 receiver)的上下文中求值另一个对象(发送者 sender)的成员(属性或方法)。委托既可以通过显式传入发送者对象实现(任何 OO 语言都支持),也可以通过语言的隐式成员查找规则实现(需要语言原生支持)。Java 中通常采用前一种显式方式,即通过构造器或方法注入持有辅助对象。
类图与时序图
类图展示了本模式在仓库中的角色划分:委托模式类图
委托模式类图:PrinterController 持有 Printer 并委托其 print 方法
时序图进一步呈现了调用方、控制器与委托对象之间的运行时交互:委托模式时序图
委托模式时序图:客户端调用 PrinterController.print,由控制器转发给具体 Printer 实现
从 delegation.urm.puml 的源码结构看,整个模块只围绕一张 Printer 接口与三个实现类展开,结构极为精简,适合作为理解委托模式的最小化示例。
适用性:何时使用委托模式
中文文档明确列出两种典型场景:
- 降低类的耦合性:调用方只依赖接口与控制器,不直接依赖具体实现类;
- 组件的行为相同,但意识到这种情况将来可能会改变:多个组件当前行为一致,未来可能分化,用委托预留替换点。
英文文档进一步细化了适用范围:
- 希望在不使用继承的情况下,将责任从一个类传递给另一个类;
- 希望实现基于组合(composition)的复用,而非基于继承(inheritance)的复用;
- 需要在运行时使用多个可互换的辅助类。
源码级实现:打印机的委托示例
仓库中的 delegation 模块完整实现了这一示例,全部源码位于 delegation/src/main/java/com/iluwatar/delegation/simple。模块采用 Lombok 的 @Slf4j 注解生成日志器,构建依赖见 delegation/pom.xml。
1. 定义统一接口
Printer.java 是控制器与所有委托类共同实现的接口,保证控制器可以“像委托对象一样”被调用:
public interface Printer {
void print(final String message);
}
该接口的 Javadoc 明确说明:接口同时被 Controller 与 Delegate 实现,控制器因此能对委托类调用相同的方法。
2. 三个具体的委托类
三个实现类分别位于 printers 子包,行为都是将消息加上品牌前缀后通过日志输出:
CanonPrinter.java、EpsonPrinter.java、HpPrinter.java 的实现结构完全一致,以 HP 为例:
@Slf4j
public class HpPrinter implements Printer {
@Override
public void print(String message) {
LOGGER.info("HP Printer : {}", message);
}
}
这三个类就是被委托的对象(delegate),它们各自持有打印这一行为的真正实现。
3. 委托控制器:不做事,只转交
PrinterController.java 是整个模式的核心——它同样实现 Printer 接口,但 print 方法体内部只是把调用转交给构造器注入的 Printer 实例:
public class PrinterController implements Printer {
private final Printer printer;
public PrinterController(Printer printer) {
this.printer = printer;
}
@Override
public void print(String message) {
printer.print(message);
}
}
其 Javadoc 点出了该设计的两重保障:一是当 Printer 的真实实现变化时,委托层依然稳定可用;二是当存在多个实现类且共享同一套委托控制时,收益最为明显。调用方(客户端)无需关心真正执行打印的是哪个类,只需持有控制器。
4. 客户端组装:运行时切换行为
App.java 演示了如何通过构造器注入不同的委托对象,在同一控制器上获得不同行为:
public class App {
private static final String MESSAGE_TO_PRINT = "hello world";
public static void main(String[] args) {
var hpPrinterController = new PrinterController(new HpPrinter());
var canonPrinterController = new PrinterController(new CanonPrinter());
var epsonPrinterController = new PrinterController(new EpsonPrinter());
hpPrinterController.print(MESSAGE_TO_PRINT);
canonPrinterController.print(MESSAGE_TO_PRINT);
epsonPrinterController.print(MESSAGE_TO_PRINT);
}
}
程序输出:
HP Printer:hello world
Canon Printer:hello world
Epson Printer:hello world
注意:这里完全不需要任何子类继承——三个控制器实例共享同一个 PrinterController 类,行为差异完全由注入的委托对象在运行时决定,这正是“组合优于继承”的直接体现。
测试验证:委托行为可被精确断言
仓库提供了两个测试文件,印证了委托调用的正确性:
- DelegateTest.java 通过一个自定义的
InMemoryAppender捕获 SLF4J 日志事件,对三种打印机分别断言PrinterController.print(...)最终输出的格式化消息内容,例如assertEquals("Canon Printer : Test Message Printed", ...)。这从测试层面证明:控制器的调用确实被转发到了被注入的委托对象,且品牌前缀由委托实现决定; - AppTest.java 验证
App.main的执行不会抛出异常。
真实世界中的应用
委托模式在 Java 生态中广泛存在,英文文档列举了三个典型实例:
- java.awt.event 包:监听器(listener)机制中,组件将事件处理委托给注册的监听器对象;
- java.util.Collections 包装类:
Collections.unmodifiableList、synchronizedList等包装类将操作委托给被包装的集合对象; - Spring 框架 IoC 容器:bean 之间大量通过依赖注入将任务委托给其他 bean 执行。
这些案例的共同特征是:外层对象暴露统一接口,内部将具体职责转交底层对象,从而在不破坏接口契约的前提下灵活替换实现。
收益与权衡
收益(Benefits)
- 减少子类化(Reduces subclassing):对象可以委托给不同对象并在运行时切换,大幅降低为每个变体写子类的需求;
- 促进复用(Encourages reuse):委托直接复用辅助对象的既有实现;
- 提升灵活性(Increases flexibility):通过切换委托对象即可在运行时改变类行为。
代价(Trade-offs)
- 运行时开销(Runtime Overhead):委托引入额外的间接层(indirection),可能带来轻微性能损耗;
- 复杂度上升(Complexity):需要额外管理接口与类来支撑委托,设计结构比直写实现更复杂。
与相关设计模式的关系
- 组合模式(Composite):可在组合模式中用于将组件特有行为委托给子组件;
- 策略模式(Strategy):委托常被用于策略模式,上下文对象把任务委托给策略对象——这与本示例中
PrinterController委托给不同Printer的思路完全同构; - 代理模式(Proxy):代理是委托的一种形式,代理对象控制对另一对象的访问,并将工作委托给它(即中文文档所称的“代理模式”别名)。
在仓库中运行与进一步探索
delegation 是 java-design-patterns 仓库的一个独立 Maven 模块,父工程坐标 com.iluwatar:java-design-patterns(版本 1.26.0-SNAPSHOT)。在仓库根目录下可执行 ./mvnw -pl delegation test 运行该模块的单元测试,./mvnw -pl delegation -am package 或直接运行 com.iluwatar.delegation.simple.App 的 main 方法即可观察委托打印输出(pom.xml 已通过 maven-assembly-plugin 配置 App 为主类)。
如需继续深入,可对照阅读:
小结:委托模式用最少的代码实现了“接口统一 + 行为可替换”的解耦目标。在 java-design-patterns 仓库中,PrinterController 与三个打印机实现共同构成了一个教科书级的示例:调用方只依赖控制器,控制器只依赖接口,真正的实现细节被完全封装在可运行时替换的委托对象中——这正是现代 Java 工程中“组合优于继承”理念的缩影。