- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
本文围绕开源仓库 java-design-patterns 中 Delegation(委托)模式 展开,系统讲解如何让一个对象对外表现某种行为、却把实际执行责任动态委派给另一个关联对象,从而在不依赖继承的前提下实现基于组合的复用。你将通过仓库中真实的Printer打印示例掌握委托模式的接口设计、委托控制器(Delegator)与具体实现(Delegate)的协作关系、运行时切换行为的用法,以及它的适用场景、优缺点和与 Proxy、Strategy、Composite 等模式的关系。
模式概览:也被称为代理(Proxy)模式
委托模式(Delegation Pattern)在传统命名中也常被称为代理模式(Proxy Pattern),属于结构型(Structural)设计模式,核心标签是解耦(Decoupling)。它描述了一种协作方式:对象对外「声称」自己能完成某项行为,但实际上把执行责任**委托(delegate)**给另一个与之关联的对象。调用方只需要面对一个统一的外观,无需关心真正干活的类是谁。
目标(Intent)
委托模式的根本目标可以概括为一句话:让一个对象对外的表现与真实执行相分离——对象可以表达某个行为,但将该行为的实现责任转交给另一个关联对象去完成。这一机制带来的直接收益是降低类与自身方法的耦合度,使行为可以在运行时被动态替换。
真实世界例子:冒险者与武器
文档中给出了一个非常直观的现实类比:
想象一群冒险者,他们根据各自的技能与能力使用不同的武器与怪物战斗。我们需要能够为他们配备不同武器,而无需修改每一种武器的源码。委托模式正是通过把工作动态委托给一个实现相关接口的特定对象来实现这一点。
在这个例子中,「冒险者」扮演委托方(Delegator),「武器」扮演被委托方(Delegate)。冒险者并不亲自实现「攻击」的细节,而是把攻击行为交给手中所持的武器对象去完成;换一把武器,攻击行为就随之改变,而冒险者本身的代码无需任何修改。
维基百科定义
关于委托,维基百科给出了如下经典定义:
在面向对象编程中,委托指的是在另一个原始对象(发送方)的上下文中,求值某个对象(接收方)的成员(属性或方法)。委托可以显式完成——通过把发送对象传递给接收对象,这在任何面向对象的语言中都可以做到;也可以隐式完成——通过语言的成员查找规则,这要求语言本身对该特性提供支持。
本仓库中的示例采用的就是显式委托:委托方在构造时将具体实现对象传入,从而把工作转交出去。
程序化示例:Java 中的打印机委托
1. 定义统一接口Printer
委托模式的第一步是抽象出一个接口,让委托方与被委托方实现同一套契约。仓库中该接口位于 Printer.java:
public interface Printer { void print(final String message); }2. 三个具体的被委托实现
接口有三个具体实现:CanonPrinter、EpsonPrinter和HpPrinter,分别对应佳能、爱普生和惠普三种打印机。它们都实现Printer接口并真正完成打印动作(这里以日志输出模拟打印):
@Slf4j public class CanonPrinter implements Printer { @Override public void print(String message) { LOGGER.info("Canon Printer : {}", message); } } @Slf4j public class EpsonPrinter implements Printer { @Override public void print(String message) { LOGGER.info("Epson Printer : {}", message); } } @Slf4j public class HpPrinter implements Printer { @Override public void print(String message) { LOGGER.info("HP Printer : {}", message); } }这三个类的仓库位置分别在 CanonPrinter.java、EpsonPrinter.java 和 HpPrinter.java。可以看到,它们才是「真正干活」的 Delegate——消息被拼接上各自的品牌前缀后输出。
3. 委托控制器PrinterController
委托方(Delegator)是PrinterController,位于 PrinterController.java。它同样实现Printer接口,但不提供打印实现,而是在构造时接收一个Printer实例,并在print方法中把工作转交出去:
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); } }从源码注释可以看出设计者的两个意图:
- 当
Printer的实际实现发生变化时,委托层依然可以正常工作; - 当存在多个实现类且共享同一套委托控制逻辑时,委托的收益最为明显。
4. 客户端使用:运行时切换行为
在客户端代码中(仓库入口见 App.java),根据传入控制器的具体打印机对象不同,打印行为也随之不同——这正是「动态委托」的体现:
private static final String MESSAGE_TO_PRINT = "hello world"; 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交互,完全不感知背后是哪个具体打印机类在执行,实现了调用方与实现方的彻底解耦。
类图与运行时协作
委托模式的结构与交互流程可用仓库中的两张图清晰呈现。首先是类图:
从类图可以看到核心结构:Printer接口定义print(String)契约;PrinterController作为委托方持有Printer类型的依赖(被委托对象);三个打印机类实现同一接口并真正完成打印。
其次是时序图,展示请求如何在 Client → Delegator → Delegate 之间流转:
时序图完整展示了委托的核心调用链:客户端向委托方发出请求 → 委托方把请求转发给被委托对象 → 被委托对象处理并返回 → 委托方再将结果回传给客户端。这与PrinterController.print()直接调用printer.print()的实现一一对应。
源码级验证:单元测试如何印证委托
仓库中的测试类 DelegateTest.java 用三个用例分别验证了委托到不同打印机的行为:
@Test void testCanonPrinter() { var printerController = new PrinterController(new CanonPrinter()); printerController.print(MESSAGE); assertEquals("Canon Printer : Test Message Printed", appender.getLastMessage()); } @Test void testHpPrinter() { var printerController = new PrinterController(new HpPrinter()); printerController.print(MESSAGE); assertEquals("HP Printer : Test Message Printed", appender.getLastMessage()); } @Test void testEpsonPrinter() { var printerController = new PrinterController(new EpsonPrinter()); printerController.print(MESSAGE); assertEquals("Epson Printer : Test Message Printed", appender.getLastMessage()); }测试通过自定义的InMemoryAppender捕获 SLF4J 日志,断言委托后输出的最终消息包含了对应打印机的品牌前缀,从测试层面证实了「请求确实被转发到了被委托对象」这一核心语义。另有 AppTest.java 用于验证程序入口可正常启动运行。
适用场景(何时使用委托模式)
根据文档与仓库实现,委托模式适用于以下情形:
- 降低方法与所属类的耦合:让对象的行为实现可以被独立替换,而不需要改动使用方代码;
- 需要一组行为一致、但未来可能变化的组件:多个组件对外表现相同接口,但具体实现可以随时在运行时切换;
- 希望用组合复用取代继承复用:当不想(或不适合)通过继承来扩展行为时,委托提供了一种「持有并转交」的替代方案;
- 需要在运行时使用多个可互换的 Helper 类:如本例中在运行时自由选用 HP、Canon、Epson 三种打印实现。
Java 生态中的真实应用
委托模式在 Java 生态中被广泛使用,文档与代码之外可以结合常见实践印证:
java.awt.event包:监听器(Listener)机制常被用于委托处理事件;- Java 集合框架的包装类(
java.util.Collections):许多包装器并不自己实现逻辑,而是把操作委托给被包装的集合对象; - Spring Framework 的 IoC 容器:Bean 之间大量通过委托协作,一个 Bean 把任务委托给其他 Bean 执行。
这些例子共同印证了委托模式「持有接口、转交实现」的核心思想在工业界的普遍价值。
优点与权衡
优点(Benefits)
- 减少子类化:对象可以把操作委托给不同对象并在运行时切换,从而显著减少为每种行为创建子类的需求;
- 促进复用:被委托对象的代码可以被多个委托方共享复用;
- 提升灵活性:通过把任务委托给 Helper 对象,类可以在运行时动态改变自身行为,而无需修改其源码。
权衡(Trade-offs)
- 运行时开销:委托引入了额外的间接层(indirection),可能带来轻微的性能成本;
- 复杂度上升:由于需要额外管理和维护参与委托的类与接口,整体设计会变得更复杂。
与相关模式的关系
- Composite(组合模式):组合模式中常借助委托,将组件特有的行为委托给子组件执行;
- Strategy(策略模式):策略模式的典型结构就是一个上下文对象把任务委托给策略对象,与委托模式高度契合;
- Proxy(代理模式):代理本质上是委托的一种形式——代理对象通过控制对另一个对象的访问,并将工作委托给该对象执行。这也解释了为什么委托模式常被称为 Proxy Pattern。
总结
委托模式提供了一条「不靠继承、靠组合」的行为复用与解耦路径:通过统一接口 + 持有引用 + 运行时转交,调用方只需面对稳定的外观,而真实实现可以被灵活替换。本仓库的 Delegation 模式模块 用 7 个 Java 类(1 个接口、1 个委托控制器、3 个打印机实现、1 个入口类)加配套单元测试,给出了一个可编译、可运行、可验证的最小完整示例,是理解并落地委托模式的理想参考。
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
Camunda服务任务:Java委托与表达式执行
Camunda服务任务:Java委托与表达式执行 引言 在企业级业务流程管理(BPM)中,服务任务(Service Task)是实现自动化业务逻辑的核心组件。C
后端工作流自动化流程编排深入解析 Java 委托模式(Delegation Pattern):用对象组合实现高效任务分派
深入解析 Java 委托模式(Delegation Pattern):用对象组合实现高效任务分派 委托模式(Delegation Pattern)是 java
示例工程教程如何通过BigFive Personality Test快速了解自己的人格特质:10个关键步骤
如何通过BigFive Personality Test快速了解自己的人格特质:10个关键步骤 想要深入了解自己的性格特征吗?BigFive Personali
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考