对象持有另一个对象,再把调用转交过去,外形都像“包一层”。判断模式时更重要的是包装目的:增加行为、转换接口、简化入口,还是控制访问。
这些是设计模式学习整理,概念与类比可结合《Head First 设计模式》阅读,不把模式名称当成某个项目的性能或安全保证。
装饰:保持契约,组合附加行为
汽车例子要让包装后的对象仍可作为 Car 使用,但包装对象与原对象不是同一个实例。下面补齐基础车和委托关系:
interface Car {
String info();
}
class RunCar implements Car {
public String info() {
return "run";
}
}
abstract class CarDecorator implements Car {
protected final Car car;
CarDecorator(Car car) {
this.car = java.util.Objects.requireNonNull(car);
}
}
class FlyCar extends CarDecorator {
FlyCar(Car car) { super(car); }
public String info() { return car.info() + ", fly"; }
}
class SwimCar extends CarDecorator {
SwimCar(Car car) { super(car); }
public String info() { return car.info() + ", swim"; }
}
在调用方法内,new SwimCar(new FlyCar(new RunCar())).info() 表达先跑、再附加飞行与游泳说明。这是行为组合示意,不是车辆能力实现;真实装饰顺序可能影响结果、异常和资源关闭。
Java I/O 是可观察的委托例子:FilterInputStream 持有底层输入流,子类可在委托基础上增加功能。不要把它误写为“装饰类必须继承同一个具体父类”;保持需要的对外契约才是关键。
适配:把已有接口转成调用方需要的接口
假设旧调用方接受 Enumeration,新数据源给的是 Iterator,可明确转换方向:
class IteratorEnumeration<E> implements java.util.Enumeration<E> {
private final java.util.Iterator<E> source;
IteratorEnumeration(java.util.Iterator<E> source) {
this.source = java.util.Objects.requireNonNull(source);
}
public boolean hasMoreElements() {
return source.hasNext();
}
public E nextElement() {
return source.next();
}
}
调用方仍看到 hasMoreElements/nextElement,内部委托 hasNext/next。方法名是 nextElement,不是 nexElement。
Enumeration API 也提示 Iterator 有可选删除操作;转换接口不会凭空提供源对象没有的行为。适配不仅是重命名,还要看异常、可修改性和生命周期约定。
外观:让调用方少了解子系统内部结构
从调用方写:
return station.getThermometer().getTemperature();
改成:
return station.getTemperature();
可以把温度计选择和读取细节收进 station,由它提供更集中、稳定的入口。两行都是依赖应用对象的方法片段,不是独立程序。
简化入口不等于把子系统全部封死,也不是禁止任何方法链。应观察内部变化是否频繁向外传播,再决定外观边界。
代理:控制真实对象的访问方式
远程代理代表别处的服务,虚拟代理可延迟创建昂贵对象,保护代理负责访问限制。RMI 只是 Java 远程调用的一种技术,不等于代理模式本身。加载中占位图可帮助理解虚拟代理,但实际 UI 更新还要遵守相应线程约定。
Java 动态代理通过接口与 InvocationHandler 路由方法,核心写法可参照 Proxy API。以下片段中接口、真实对象和策略均由项目提供:
InvocationHandler handler = (proxy, method, args) -> {
try {
return method.invoke(target, args);
} catch (InvocationTargetException failure) {
throw failure.getCause();
}
};
Object wrapped = Proxy.newProxyInstance(
Api.class.getClassLoader(),
new Class<?>[] { Api.class },
handler);
涉及的类型来自 java.lang.reflect。示例只演示委托与异常展开,没有访问检查;打印日志也不会自动变成保护代理。运行时生成代理类不等于先生成 Java 源码再调用 javac,具体字节码实现应按 JDK 版本核对。
装饰偏向附加职责,适配改变调用方看到的接口,外观收拢子系统入口,代理控制访问边界。它们可以组合,甚至共享结构,但不应靠“都用了委托”就当成同一种设计。