news 2026/10/1 17:57:14

java-design-patterns 中的 Delegation 委托模式:Java 运行时动态任务委托的权威实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java-design-patterns 中的 Delegation 委托模式:Java 运行时动态任务委托的权威实践指南
  • 示例工程
  • 教程

【免费下载链接】java-design-patterns

Design patterns implemented in Java

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载

导读

本文围绕开源仓库 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

项目地址:https://gitcode.com/GitHub_Trending/ja/java-design-patterns
点击查看免费下载
上一篇:Windows窗口置顶神器AlwaysOnTop:让重要窗口始终在最前面,工作效率提升300%
下一篇:HyperFrames v0.7.72 值得升级吗:分布式渲染与确定性媒体处理 3 大变化讲透指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 17:56:41

iOS应用安全加固实战:从逆向工具路径分析到代码混淆与运行时防护

很多团队把“iOS应用安全加固”理解成“找一款代码混淆工具跑一遍,然后上架”。这种想法我见过太多次了,结果往往就是开发同学忙了一周,最终包交到我手里,我用一台越狱设备加一把class-dump,十几分钟就把核心方法列表原…

作者头像 李华
网站建设 2026/10/1 17:55:16

非机动车违规停放检测实战:从数据清洗到树莓派部署

简介:本资源是面向机器视觉算法工程师与智能交通项目开发者的YOLOv5专用非机动车识别数据集子集,聚焦电动车违规停放场景的模型训练与检测验证。资源包含E_bicycle2类别共994张高质量JPG图像及配套PASCAL VOC格式XML标注文件(总计1976个文件&…

作者头像 李华
网站建设 2026/10/1 17:54:37

精密光时频传递:从光纤到星地链路,探索频率同步的极限

这次分享的笔记有点特殊。标题里的“AI笔记”不是套壳写法——我确实用大模型把二十多篇光时频传递相关的论文、技术报告和实验数据先梳成提纲,再逐条回原文核对公式、参数和图表细节,最后落成这份核心内容总结。精密光时频传递,简单说就是把…

作者头像 李华
网站建设 2026/10/1 17:54:23

gcc与g++的区别:用gcc编译C++的方法与常见坑

那段时间我频繁在 Linux 命令行下处理 C/C 小项目,最常用的一条命令就是gcc -o demo demo.c。直到有一次我改了后缀名,把源码保存成demo.cpp,依然习惯性敲下gcc -o demo demo.cpp,结果终端刷出一堆undefined reference to std::co…

作者头像 李华
网站建设 2026/10/1 17:53:49

弶港2026年3月15日潮汐解读:小潮汛赶海窗口与实操

话说弶港这片滩涂,时间从来不是看钟表,而是看潮水。赶过海、钓过鱼的朋友都懂,你在弶港做的每一件事——几时下滩、几时收网、几时返岸、几时把船推进浪里——全由一张潮汐表说了算。2026年春天的第一次大潮汛眼看就要来了,很多人…

作者头像 李华
网站建设 2026/10/1 17:53:40

微电网日前经济调度实战:风光储建模、Yalmip求解与避坑指南

拿到"基于风光储能和需求响应的微电网日前经济调度"这种题目,很多人第一反应是赶紧找个Matlab代码跑起来,结果折腾一周发现,问题根本不在算法,而在建模本身——储能SOC怎么算、需求响应怎样进入目标函数、功率平衡等式怎…

作者头像 李华