文章目录
Java 代理模式和适配器模式
代理模式(Proxy Pattern)
- 特点
- 代理对象充当目标对象的替代品。
- 代理对象可以对目标对象的访问进行控制或增强。
- 常见场景:访问控制、性能优化(如懒加载)、日志记录、分布式调用(如 RPC)等。
适配器模式(Adapter Pattern)
- 特点
- 适配器充当“中间翻译者”,解决接口不匹配的问题。
- 常见场景:兼容老系统(向后兼容)、集成第三方库,或者对接口的统一抽象。
代理模式和适配器模式的区别
对比点 | 代理模式 | 适配器模式 |
---|---|---|
主要目标 | 控制对目标对象的访问或增加功能 | 解决两个接口不兼容的问题 |
角色 | 代理对象、目标对象 | 目标对象、适配器、需要适配的接口 |
使用场景 | 访问控制、权限校验、日志记录、性能优化、分布式调用等 | 将现有的类或接口适配为目标接口,解决接口不兼容的问题 |
实现方式 | 通过代理类(静态代理或动态代理)实现对目标对象方法的增强 | 通过适配器类实现接口的转换 |
是否增强功能 | 代理模式可以对目标对象的功能进行增强 | 适配器不增强功能,只是转换接口 |
代理模式的使用举例
Java 中代理模式可以分为两种实现方式:
- 静态代理:代理类由手动编写,在编译时确定。
- 动态代理:代理类在运行时动态生成,常用的是 Java 自带的动态代理(基于
java.lang.reflect.Proxy
)和 CGLIB 动态代理。
静态代理实现:用代理模式记录方法调用日志
目标接口:
java">public interface Service {void doTask();
}public class RealService implements Service {@Overridepublic void doTask() {System.out.println("RealService: 执行任务");}
}
静态代理类:
java">public class LoggingProxy implements Service {private final Service realService;public LoggingProxy(Service realService) {this.realService = realService;}@Overridepublic void doTask() {System.out.println("LoggingProxy: 开始执行任务...");realService.doTask();System.out.println("LoggingProxy: 任务执行完成");}
}
测试代码:
java">public class StaticProxyExample {public static void main(String[] args) {Service realService = new RealService();Service proxy = new LoggingProxy(realService);proxy.doTask(); // 使用代理对象调用方法}
}
- 输出
LoggingProxy: 开始执行任务...
RealService: 执行任务
LoggingProxy: 任务执行完成
动态代理实现:使用 Java 动态代理记录方法调用日志
- 动态代理更加灵活,适合在运行时为任意接口生成代理。
目标接口和实现:
java">public interface Service {void doTask();
}public class RealService implements Service {@Overridepublic void doTask() {System.out.println("RealService: 执行任务");}
}
动态代理处理类:
java">import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class LoggingHandler implements InvocationHandler {private final Object target;public LoggingHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("LoggingHandler: 方法 " + method.getName() + " 开始执行");Object result = method.invoke(target, args);System.out.println("LoggingHandler: 方法 " + method.getName() + " 执行完成");return result;}
}
测试代码:
java">public class DynamicProxyExample {public static void main(String[] args) {Service realService = new RealService();// 创建动态代理Service proxy = (Service) Proxy.newProxyInstance(realService.getClass().getClassLoader(),realService.getClass().getInterfaces(),new LoggingHandler(realService));proxy.doTask(); // 使用动态代理调用方法}
}
- 输出
LoggingHandler: 方法 doTask 开始执行
RealService: 执行任务
LoggingHandler: 方法 doTask 执行完成
适配器模式使用举例
- 适配器模式通常用于解决接口不兼容的问题,例如当我们需要将一个老接口适配为新的接口,或者需要集成第三方库时。
示例:适配老版本接口
- 定义目标接口和老版本接口:
java">// 新接口
public interface NewInterface {void newMethod();
}// 老接口
public class OldClass {public void oldMethod() {System.out.println("OldClass: 调用旧方法");}
}
- 适配器类:
java">public class Adapter implements NewInterface {private final OldClass oldClass;public Adapter(OldClass oldClass) {this.oldClass = oldClass;}@Overridepublic void newMethod() {// 在新方法中调用旧方法oldClass.oldMethod();}
}
- 测试代码:
java">public class AdapterExample {public static void main(String[] args) {OldClass oldClass = new OldClass();NewInterface adapter = new Adapter(oldClass);adapter.newMethod(); // 调用新接口的方法,但实际调用的是旧接口的方法}
}
- 输出
OldClass: 调用旧方法
适配器模式扩展:对象适配 vs 类适配
- 对象适配器:通过组合(如上述示例中,适配器持有一个
OldClass
对象)。 - 类适配器:通过继承来实现适配,适配器继承旧类并实现目标接口(要求旧类不能是
final
,且适配器只能适配一个类)。
代理与适配器模式使用建议
使用代理模式的时机
- 场景 1:需要为对象添加额外功能,而又不想直接修改目标对象的代码(如访问控制、日志记录等)。
- 场景 2:需要延迟加载目标对象,或者在访问目标对象时添加缓存机制。
- 推荐使用动态代理
- 如果需要代理多个类或接口,动态代理更灵活且代码量更少。
- 如果只针对一个类或接口,可以直接使用静态代理。
使用适配器模式时机
- 场景 1:需要整合现有的旧系统或第三方库,而它们的接口与当前系统的接口不兼容。
- 场景 2:需要对不同类的接口进行统一,提供一个一致的调用方法。
- 建议:
- 如果需要适配的类较多,可以考虑通过继承或组合方式实现适配器。
- 如果适配的接口或类较复杂,可以结合工厂模式,统一管理适配器的实例化。
总结
模式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
代理模式 | 功能增强(日志、权限、远程调用等) | 灵活添加功能,解耦目标对象和功能实现 | 静态代理增加代码量,动态代理可能增加复杂度 |
适配器模式 | 接口不兼容(老接口适配新接口、第三方库整合等) | 解决接口兼容问题,增强代码复用性 | 若接口变化频繁,适配器可能需要频繁修改 |