代理模式详解:代理、策略与模板方法模式

引言

设计模式是面向对象编程中的经典解决方案,它们封装了前人的经验,提供了可复用的设计思路。本文将重点介绍三种常用的设计模式:代理模式(含静态代理、JDK动态代理、CGLIB代理)、策略模式模板方法模式,并通过代码示例和原理分析帮助读者理解其应用场景和实现方式。

一、代理模式(Proxy Pattern)

1.1 定义与应用场景

代理模式为其他对象提供一种代理以控制对这个对象的访问。核心作用是在不修改目标对象的前提下,通过引入代理对象增强目标对象的功能(如日志记录、事务管理、权限控制等)。

典型应用场景

  • Spring AOP的方法增强
  • RPC框架的服务调用
  • 延迟加载(如Hibernate的懒加载)
  • 权限校验与日志记录

1.2 静态代理

实现原理

静态代理在编译期手动创建代理类,代理类与目标类实现同一接口,并持有目标对象的引用,在调用目标方法前后添加增强逻辑。

代码示例:日志代理
// 1. 定义服务接口
public interface IService {void serve();
}// 2. 实现目标类
public class RealService implements IService {@Overridepublic void serve() {System.out.println("真实服务: 处理核心业务逻辑");}
}// 3. 创建静态代理类
public class StaticProxy implements IService {private final IService target; // 持有目标对象引用public StaticProxy(IService target) {this.target = target;}@Overridepublic void serve() {// 增强逻辑:方法调用前System.out.println("[静态代理] 记录调用开始时间");// 调用目标方法target.serve();// 增强逻辑:方法调用后System.out.println("[静态代理] 记录调用结束时间");}
}// 4. 客户端调用
public class Client {public static void main(String[] args) {IService realService = new RealService();IService proxy = new StaticProxy(realService);proxy.serve();}
}
输出结果:
[静态代理] 记录调用开始时间
真实服务: 处理核心业务逻辑
[静态代理] 记录调用结束时间
优缺点
  • 优点:实现简单,性能较高(编译期确定代理类)。
  • 缺点
    • 代码冗余:每个目标类需对应一个代理类
    • 维护成本高:接口变更时需同步修改代理类

1.3 JDK动态代理

实现原理

JDK动态代理通过反射机制在运行时动态生成代理类,无需手动编写代理类。核心类为java.lang.reflect.ProxyInvocationHandler接口。

  • Proxy类:生成代理对象的工厂类,通过newProxyInstance()方法创建代理实例。
  • InvocationHandler接口:定义代理逻辑的接口,需实现invoke()方法处理增强逻辑。
代码示例:动态日志代理
// 1. 目标接口与实现类(复用静态代理的IService和RealService)// 2. 实现InvocationHandler
public class LogInvocationHandler implements InvocationHandler {private final Object target; // 目标对象public LogInvocationHandler(Object target) {this.target = target;}// 代理逻辑:所有方法调用都会转发到invoke()@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("[JDK动态代理] 方法" + method.getName() + "调用开始");Object result = method.invoke(target, args); // 反射调用目标方法System.out.println("[JDK动态代理] 方法" + method.getName() + "调用结束");return result;}// 创建代理对象public Object getProxy() {return Proxy.newProxyInstance(target.getClass().getClassLoader(), // 类加载器target.getClass().getInterfaces(),   // 目标类实现的接口this                                // InvocationHandler实例);}
}// 3. 客户端调用
public class Client {public static void main(String[] args) {IService realService = new RealService();IService proxy = (IService) new LogInvocationHandler(realService).getProxy();proxy.serve();}
}
底层原理分析
  1. 代理类生成Proxy.newProxyInstance()通过ProxyGenerator生成代理类字节码,类名格式为com.sun.proxy.$ProxyN
  2. 方法转发:生成的代理类实现目标接口,并重写方法,将调用转发到InvocationHandler.invoke()
  3. 反射调用invoke()方法通过Method.invoke()反射调用目标方法,实现增强逻辑与业务逻辑的解耦。
局限性
  • 必须实现接口:JDK动态代理只能代理实现了接口的类。
  • 性能开销:反射调用比直接调用慢,适合代理逻辑复杂但调用频率低的场景。

1.4 CGLIB动态代理

实现原理

CGLIB(Code Generation Library)通过字节码生成技术动态创建目标类的子类,并重写非final方法实现代理。核心类为EnhancerMethodInterceptor接口。

  • Enhancer:CGLIB的核心类,用于创建代理对象。
  • MethodInterceptor:方法拦截器接口,需实现intercept()方法定义增强逻辑。
代码示例:CGLIB代理
// 1. 引入CGLIB依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>// 2. 目标类(无需实现接口)
public class UserService {public void createUser(String name) {System.out.println("创建用户: " + name);}
}// 3. 实现MethodInterceptor
public class LogMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("[CGLIB代理] 方法" + method.getName() + "调用开始");Object result = proxy.invokeSuper(obj, args); // 调用父类(目标类)方法System.out.println("[CGLIB代理] 方法" + method.getName() + "调用结束");return result;}
}// 4. 创建代理对象
public class Client {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(UserService.class); // 设置父类(目标类)enhancer.setCallback(new LogMethodInterceptor()); // 设置拦截器UserService proxy = (UserService) enhancer.create(); // 创建代理对象proxy.createUser("张三");}
}
底层原理分析
  1. 子类生成:CGLIB使用ASM库生成目标类的子类,类名格式为目标类名$$EnhancerByCGLIB$$随机字符串
  2. 方法重写:子类重写目标类的非final方法,在方法中调用MethodInterceptor.intercept()
  3. 字节码操作:直接修改字节码,比JDK动态代理性能更高(避免反射调用)。
局限性
  • 无法代理final类/方法:由于基于继承,final类或方法无法被重写。
  • 安全性风险:字节码操作可能绕过权限检查,需谨慎使用。

1.5 三种代理方式对比

特性静态代理JDK动态代理CGLIB代理
实现方式手动编写代理类反射+接口ASM字节码+继承
代理目标接口或类仅接口类(非final)
性能高(直接调用)中(反射调用)高(字节码生成)
灵活性低(编译期确定)中(运行时生成)高(运行时生成)
典型应用简单增强场景Spring AOP(接口代理)Spring AOP(类代理)

二、策略模式(Strategy Pattern)

2.1 定义与应用场景

策略模式定义一系列算法,将每个算法封装为独立的策略类,并使它们可相互替换。核心作用是消除复杂的条件判断(if-else/switch),实现算法的动态切换。
在这里插入图片描述

典型应用场景

  • 支付方式选择(支付宝、微信、银行卡)
  • 排序算法切换(快速排序、冒泡排序)
  • 折扣策略(满减、打折、优惠券)

2.2 结构与实现

核心角色
  • 策略接口(Strategy):定义算法的公共接口。
  • 具体策略(Concrete Strategy):实现策略接口的具体算法。
  • 上下文(Context):持有策略对象的引用,负责调用策略。
代码示例:支付策略
// 1. 策略接口
public interface PaymentStrategy {void pay(double amount);
}// 2. 具体策略:支付宝支付
public class AlipayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付: " + amount + "元");}
}// 3. 具体策略:微信支付
public class WechatPayStrategy implements PaymentStrategy {@Overridepublic void pay(double amount) {System.out.println("使用微信支付: " + amount + "元");}
}// 4. 上下文类
public class PaymentContext {private PaymentStrategy strategy;// 构造器注入策略public PaymentContext(PaymentStrategy strategy) {this.strategy = strategy;}// 动态切换策略public void setStrategy(PaymentStrategy strategy) {this.strategy = strategy;}// 执行支付public void executePayment(double amount) {strategy.pay(amount);}
}// 5. 客户端调用
public class Client {public static void main(String[] args) {PaymentContext context = new PaymentContext(new AlipayStrategy());context.executePayment(100); // 使用支付宝支付context.setStrategy(new WechatPayStrategy());context.executePayment(200); // 切换为微信支付}
}
输出结果:
使用支付宝支付: 100.0元
使用微信支付: 200.0元

2.3 优缺点

  • 优点

    • 符合开闭原则:新增策略无需修改原有代码。
    • 消除条件语句:用多态替代if-else判断。
    • 算法复用:策略类可在不同场景复用。
  • 缺点

    • 策略类数量增多:每个策略对应一个类。
    • 客户端需了解策略:客户端需知道所有策略并选择合适的策略。

三、模板方法模式(Template Method Pattern)

3.1 定义与应用场景

模板方法模式定义算法的骨架,将某些步骤延迟到子类实现。核心作用是复用公共流程,差异化实现细节,确保算法结构的稳定性。

典型应用场景

  • 框架设计(如Spring的JdbcTemplate)
  • 固定流程的业务逻辑(如报表生成、数据导入)
  • 生命周期管理(如Servlet的init-service-destroy)

3.2 结构与实现

核心角色
  • 抽象类(Abstract Class):定义模板方法(算法骨架)和基本方法(抽象方法、具体方法、钩子方法)。
  • 具体子类(Concrete Class):实现抽象方法,覆盖钩子方法(可选)。
代码示例:饮料制作流程
// 1. 抽象类(模板)
public abstract class Beverage {// 模板方法:定义算法骨架(final防止子类修改流程)public final void prepareRecipe() {boilWater();       // 公共步骤brew();            // 抽象步骤(子类实现)pourInCup();       // 公共步骤if (customerWantsCondiments()) { // 钩子方法控制流程addCondiments(); // 抽象步骤(子类实现)}}// 抽象方法:冲泡(子类实现)protected abstract void brew();// 抽象方法:加调料(子类实现)protected abstract void addCondiments();// 具体方法:烧水(公共步骤)private void boilWater() {System.out.println("烧水");}// 具体方法:倒入杯子(公共步骤)private void pourInCup() {System.out.println("倒入杯子");}// 钩子方法:是否加调料(默认加)protected boolean customerWantsCondiments() {return true;}
}// 2. 具体子类:咖啡
public class Coffee extends Beverage {@Overrideprotected void brew() {System.out.println("冲泡咖啡粉");}@Overrideprotected void addCondiments() {System.out.println("加糖和牛奶");}// 覆盖钩子方法:询问用户是否加调料@Overrideprotected boolean customerWantsCondiments() {return askUser(); // 模拟用户输入}private boolean askUser() {System.out.println("是否加糖和牛奶?(y/n)");return true; // 简化示例,默认返回true}
}// 3. 具体子类:茶
public class Tea extends Beverage {@Overrideprotected void brew() {System.out.println("浸泡茶叶");}@Overrideprotected void addCondiments() {System.out.println("加柠檬");}
}// 4. 客户端调用
public class Client {public static void main(String[] args) {Beverage coffee = new Coffee();coffee.prepareRecipe();// 输出:烧水 → 冲泡咖啡粉 → 倒入杯子 → 是否加糖和牛奶? → 加糖和牛奶Beverage tea = new Tea();tea.prepareRecipe();// 输出:烧水 → 浸泡茶叶 → 倒入杯子 → 加柠檬}
}

3.3 钩子方法的作用

钩子方法(Hook Method)是模板方法模式的关键扩展点,用于:

  • 控制流程:如示例中customerWantsCondiments()决定是否加调料。
  • 提供默认实现:子类可选择是否覆盖。
  • 扩展功能:在不修改模板方法的前提下增加新逻辑。

3.4 优缺点

  • 优点

    • 代码复用:公共流程在父类中实现,子类共享。
    • 强制流程:子类无法修改算法骨架,确保一致性。
    • 扩展性好:子类通过实现抽象方法扩展功能。
  • 缺点

    • 增加抽象类:系统复杂度提高。
    • 子类依赖父类:父类修改可能影响所有子类。

四、总结

三种模式的对比与选择

模式核心思想典型应用场景关键角色/类
代理模式控制对象访问,增强功能AOP、权限控制、延迟加载Proxy、InvocationHandler
策略模式封装算法,动态切换支付方式、排序算法、折扣策略Strategy接口、Context
模板方法固定流程,延迟步骤实现框架设计、固定流程业务逻辑抽象类(模板方法+钩子方法)

实践建议

  1. 代理模式:优先使用JDK动态代理(接口代理),无接口时选择CGLIB。
  2. 策略模式:当存在3个以上可替换算法时使用,结合工厂模式管理策略。
  3. 模板方法:流程固定但步骤实现可变时使用,通过钩子方法提供灵活性。

设计模式的核心价值在于解耦与复用,实际开发中需结合业务场景灵活选择,避免过度设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/592.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kotlin Map映射转换

Kotlin 集合转换&#xff1a;map、mapIndexed、mapNotNull、mapKeys、mapValues、flatten、flatMap 引言 在之前的主题中&#xff0c;我们学习了如何筛选&#xff08;filter&#xff09;和排序&#xff08;sort&#xff09;集合。然而&#xff0c;处理集合时最重要的任务之一是…

python+Request提取cookie

目录 参数 登录接口参数 ​编辑 查询所有课程参数 python处理cookie。 提取cookie 使用json模块提取token 正则表达式re模块提取token 完整代码 参数 登录接口参数 查询所有课程参数 python处理cookie。 导入request包 # 导入request包 import requests 编写登录请求的查询参…

C++类与对象(上)

1.类的定义 1.1类定义格式 • class为定义类的关键字&#xff0c;Stack为类的名字&#xff08;跟结构体名类似&#xff09;&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。类体中内容称为类的成员&#xff1a;类中的变量称为类的属性或成员变量&a…

Cesium加载3DTiles模型并且重新设置3DTiles模型的高度

代码&#xff1a; 使用的时候&#xff0c;直接调用 load3DTiles() 方法既可。 // 加载3Dtiles const load3DTiles async () > {let tiles_url "/3DTiles2/Production_1.json";let tileset await Cesium.Cesium3DTileset.fromUrl(tiles_url, {enableCollision: …

JavaSE-多态

多态的概念在完成某个行为时&#xff0c;不同的对象在完成时会呈现出不同的状态。比如&#xff1a;动物都会吃饭&#xff0c;而猫和狗都是动物&#xff0c;猫在完成吃饭行为时吃猫粮&#xff0c;狗在完成吃饭行为时吃狗粮&#xff0c;猫和狗都会叫&#xff0c;狗在完成这个行为…

MySQL SQL语句精要:DDL、DML与DCL的深度探究

在数据库技术的浩瀚星空中&#xff0c;MySQL犹如一颗璀璨的星辰&#xff0c;以其卓越的性能、灵活的架构以及广泛的适用性&#xff0c;深受全球众多开发者的青睐。而 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;作为与数据库交互的核心语…

MYSQL笔记2

创建表&#xff1a; 格式&#xff1a; create table 表名(表选项) 表定义选项格式为&#xff1a; 列名1 列类型1 约束, 列名2 列类型2 约束,…… 默认的情况是&#xff0c;表被创建到当前的数据库中。若表已存在、没有当前数据库或者数据库不存在&#xff0c;则会出现错误 使…

Linux修炼:开发工具

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C修炼之路》、《Linux修炼&#xff1a;终端…

基于 CentOS 7 的 LVS+DR+Web+NFS 旅游攻略分享平台部署

1 项目概述 1.1 旅游攻略平台项目背景 随着互联网旅游行业的快速发展&#xff0c;用户对旅游攻略分享平台的高可用性和稳定性提出了更高要求。传统单服务器架构在面对高并发访问时容易出现性能瓶颈&#xff0c;导致响应延迟甚至服务中断。本项目基于 LVSDRWebNFS 架构&#x…

python-enumrate函数

文章目录基本语法基本用法基本遍历指定起始索引实际应用场景需要索引的循环创建字典映射处理文件行号与range(len())对比注意事项enumerate()是Python内置函数&#xff0c;用于在遍历序列&#xff08;如列表、元组或字符串&#xff09;时同时获取索引和值。基本语法 enumerate…

linux_线程概念

线程特征&#xff1a;是进程内的执行分支&#xff0c;线程的执行粒度&#xff0c;要比进程要细。1.理解Linux线程线程<执行流<进程 线程实际上是复用进程的数据结构和管理算法&#xff0c;进程的task struct&#xff0c;实际上是模拟线程&#xff0c;部分书中说Linux没有…