在Java中如何实现对象行为的委托_JavaDelegationPattern应用解析

Java中用组合代替继承实现委托,关键在于接口定义、字段封装和方法转发,委托类只暴露必要行为,需显式处理equals/hashCode/toString,泛型委托要注意类型擦除,且须厘清委托、代理与装饰器的职责边界。

Java里用组合代替继承做行为委托,关键在接口和字段设计

Java没有原生的委托语法(比如 Kotlin 的 by),但通过接口 + 成员字段 + 方法转发,就能干净实现委托模式。核心不是“怎么写”,而是“谁该暴露什么方法”——委托类只暴露被委托对象的**必要行为**,不泄露内部细节或冗余方法。

  • 先定义清晰的接口(如 DataSourceLogger),这是委托契约的基础
  • 委托者类(如 UserService)持有一个该接口的字段(private final DataSource dataSource
  • 所有需要委托的方法,直接调用该字段对应方法,不做额外逻辑——有逻辑就说明不该委托,该继承或策略化
  • 避免在委托方法里加空值检查或日志:那是代理(Proxy)或装饰器(Decorator)的事,不是委托

委托时绕不开的 equals/hashCode/toString 怎么处理

如果委托类重写了 equalshashCode,又依赖被委托对象的实现,必须显式转发,否则默认是引用比较,会破坏逻辑一致性。JDK 本身不帮你做这件事。

public class UserService {
    private final DataSource dataSource;

    public UserService(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserService that = (UserService) o;
        return Objects.equals(dataSource, that.dataSource); // 显式委托到 dataSource.equals()
    }

    @Override
    public int hashCode() {
        return Objects.hash(dataSource); // 同样委托
    }
}
  • toString() 同理:若需体现委托对象状态,应包含 dataSource.toString(),而不是只写类名
  • 不要盲目用 Lombok 的 @EqualsAndHashCode(of = "dataSource")——它生成的是字段值比较,但若 dataSource 本身没正确实现 equals,结果仍错
  • 如果委托对象是不可变且已正确定义了这些方法(如 StringUUID),可放心转发;否则得先确认它的契约

泛型委托类如何避免类型擦除导致的运行时问题

写一个通用委托容器(如 DelegatingList)时,构造时传入的 List 在运行时只剩 List,但多数场景下只要方法签名一致,不影响委托行为。真正出问题的是反射或序列化场景。

  • 别在委托类里用 getClass().getGenericSuperclass() 去提取 T 类型——擦除后拿不到,会得到 Object
  • 如果必须保留类型信息(比如做 JSON 反序列化),构造时额外传入 TypeReference>Class 参数
  • Spring 的 DelegatingFilterProxy 就是典型泛型委

    托,但它靠 Bean 名称和上下文定位目标对象,不依赖泛型运行时信息

和代理(Proxy)、装饰器(Decorator)混用时,职责必须划清

委托(Delegation)只做「把请求转给另一个对象」,不改变行为语义;代理加控制逻辑(如权限、事务),装饰器增强行为(如缓存、日志)。三者代码结构相似,但意图不同,混用会导致维护困难。

立即学习“Java免费学习笔记(深入)”;

  • java.lang.reflect.Proxy 或 CGLIB 是动态代理,适合切面场景;委托是静态、编译期确定的
  • 装饰器通常实现同一接口并持有被装饰对象,但会在方法前后加逻辑;委托则严格“原样转发”
  • 一个类同时做委托 + 日志记录?那它已经不是委托者,是装饰器了——此时应拆成 LoggingUserService 包裹 UserService,后者再委托 DataSource

委托真正的难点不在写法,而在于判断:这个行为,到底该由当前对象自己承担,还是交给别人?一旦委托关系过深(A→B→C→D),链路就难追踪,这时候就得考虑是否该用事件驱动或命令总线来解耦。