Java 策略模式从入门到实战(后端必看,附案例+面试考点)
前言:策略模式(Strategy Pattern)是Java设计模式中最实用的"行为型模式"之一,核心是"将算法/行为封装成独立策略类,让算法可动态切换、复用,且不影响客户端代码"。
很多Java后端开发者在开发中会遇到这样的问题:面对多种相似的业务逻辑(如多种支付方式、多种排序算法、多种校验规则),习惯用大量if-else或switch-case判断,导致代码臃肿、难以维护、扩展性差;面试时被问到"如何优化大量if-else""策略模式的核心是什么""策略模式在框架中的应用",常常答不全面。
本文从入门到实战,用极简语言拆解策略模式核心,结合可直接复制运行的入门案例、真实业务实战(Spring Boot环境),以及高频面试考点,带你吃透策略模式------新手能快速上手,中级开发者能落地项目,面试时能轻松应对追问,看完就能用在实际开发中。
一、为什么Java后端必须掌握策略模式?(痛点直击)
先看3个Java后端开发中最常见的场景,你一定遇到过,这也是策略模式的核心应用场景,更是面试中高频提及的"if-else优化"典型场景:
-
场景1:电商支付系统,支持支付宝、微信支付、银联支付3种方式,不同支付方式的逻辑不同,若用if-else判断"支付类型",后续新增"京东支付""PayPal支付",需修改原有代码,违背开闭原则;
-
场景2:后台管理系统的排序功能,支持按时间排序、按金额排序、按名称排序,不同排序逻辑独立,若用switch-case整合,代码会随着排序方式增加而变得冗长,难以维护;
-
场景3:接口参数校验,不同接口的校验规则不同(如登录校验手机号+密码,注册校验手机号+验证码+密码),若将所有校验逻辑写在一个方法里,会导致方法臃肿,复用性极差。
这些场景的共性问题:多种相似算法/行为耦合在一起,扩展性差、维护成本高、代码冗余。
而策略模式的核心价值,就是"解耦"------将每种算法/行为封装成独立的"策略类",客户端通过统一接口调用不同策略,无需关心策略的具体实现,新增策略时无需修改原有代码,完美符合"开闭原则"。
简单说,策略模式就是"把不同的做法(策略)分开装,想用哪个就用哪个,不用改原来的代码"。比如支付系统,新增京东支付,只需新增一个京东支付策略类,无需修改支付核心逻辑,极大提升开发效率和代码可维护性。
核心结论:策略模式不是"花里胡哨"的设计,而是后端开发的"刚需优化手段"------初级开发者用它优化if-else,中级开发者用它设计可扩展的业务架构,高级开发者用它理解框架底层设计(如Spring的BeanPostProcessor、MyBatis的插件机制),面试时更是高频考点(中高级岗位必问)。
二、策略模式核心概念(极简入门,无需死记硬背)
策略模式的本质很简单:将一组可替换的算法封装成独立的策略类,通过一个统一的上下文类,动态切换不同的策略,客户端只需与上下文交互,无需直接操作具体策略。
就像电商支付:支付行为(算法)有支付宝、微信、银联3种,每种支付方式都是一个"策略类";上下文类(支付管理器)负责接收客户端的支付请求,动态选择对应的支付策略,客户端只需告诉上下文"要哪种支付方式",无需关心支付的具体实现。
2.1 核心角色(4个核心,必记,区分角色是掌握策略模式的关键)
-
抽象策略角色(Strategy):定义所有具体策略的统一接口,声明策略的核心方法(如支付、排序、校验),是策略模式的"规范",确保所有具体策略都有统一的调用方式;
-
具体策略角色(ConcreteStrategy):实现抽象策略接口,封装具体的算法/行为(如支付宝支付逻辑、按时间排序逻辑),是策略模式的"具体实现";
-
上下文角色(Context):维护一个抽象策略的引用,提供统一的接口供客户端调用,负责动态切换策略、传递参数,是客户端与具体策略之间的"桥梁";
-
客户端(Client):负责创建具体策略对象和上下文对象,通过上下文调用具体策略,无需直接操作具体策略类,降低耦合。
核心原则:抽象策略统一接口,具体策略实现细节,上下文管理策略,客户端调用上下文。这是策略模式的灵魂,也是它与其他行为型模式(如模板方法模式)的关键区别。
核心注意点:策略模式的核心是"算法/行为的封装与切换",解决的是"多种相似算法耦合"的问题,与装饰器模式的"功能增强"、组合模式的"树形结构管理"有本质区别。
2.2 策略模式的核心流程(一句话看懂)
客户端创建具体策略对象 → 上下文对象持有该策略对象 → 客户端通过上下文调用策略的核心方法 → 如需切换策略,只需给上下文重新设置新的策略对象,无需修改其他代码。
三、策略模式入门实现(附可复制代码,新手必练)
以"电商支付系统"为入门案例,模拟3种支付方式(支付宝、微信、银联),用策略模式实现"支付方式动态切换",对比"普通if-else实现"和"策略模式实现"的差异,一看就懂、一练就会。
3.1 普通实现:if-else耦合的反例(痛点凸显)
若不使用策略模式,直接用if-else判断支付类型,代码会随着支付方式的增加而变得臃肿,扩展性极差,后续新增支付方式需修改原有代码。
java
// 支付工具类(if-else耦合实现)
public class PaymentUtil {
// 支付方法,根据支付类型选择不同支付逻辑
public void pay(String paymentType, double amount) {
if ("alipay".equals(paymentType)) {
// 支付宝支付逻辑
System.out.println("使用支付宝支付,金额:" + amount);
// 实际开发中,这里会有复杂的签名、请求调用逻辑
} else if ("wechat".equals(paymentType)) {
// 微信支付逻辑
System.out.println("使用微信支付,金额:" + amount);
} else if ("unionpay".equals(paymentType)) {
// 银联支付逻辑
System.out.println("使用银联支付,金额:" + amount);
} else {
throw new UnsupportedOperationException("不支持该支付方式");
}
}
}
// 客户端调用
public class Client {
public static void main(String[] args) {
PaymentUtil paymentUtil = new PaymentUtil();
// 支付宝支付
paymentUtil.pay("alipay", 100.0);
// 微信支付
paymentUtil.pay("wechat", 200.0);
// 痛点:新增京东支付,需修改PaymentUtil的pay方法,新增else if判断
// 违背开闭原则,代码臃肿,维护成本高
}
}
【运行结果】:
text
使用支付宝支付,金额:100.0
使用微信支付,金额:200.0
【缺点极其明显】:
-
代码耦合严重:所有支付逻辑都写在一个方法里,if-else过多,代码冗长,可读性差;
-
扩展性差:新增支付方式(如京东支付),需修改原有PaymentUtil类,违背"开闭原则";
-
复用性差:支付逻辑无法复用,若其他地方需要单独调用支付宝支付,需重复编写代码;
-
维护成本高:若某一种支付逻辑需要修改,需在冗长的if-else中找到对应代码,容易出错。
3.2 策略模式实现:解耦的优雅代码(正例)
用策略模式重构支付系统,将每种支付方式封装成独立策略类,通过上下文类统一管理,客户端无需关心具体支付逻辑,新增支付方式只需新增策略类,无需修改原有代码。
java
// 1. 抽象策略角色:定义支付策略的统一接口
public interface PaymentStrategy {
// 核心方法:支付
void pay(double amount);
}
// 2. 具体策略角色1:支付宝支付
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
// 模拟支付宝支付的复杂逻辑(签名、请求第三方接口等)
System.out.println("支付宝支付 - 签名验证通过");
System.out.println("使用支付宝支付,金额:" + amount);
System.out.println("支付宝支付成功\n");
}
}
// 2. 具体策略角色2:微信支付
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("微信支付 - 公众号授权通过");
System.out.println("使用微信支付,金额:" + amount);
System.out.println("微信支付成功\n");
}
}
// 2. 具体策略角色3:银联支付
public class UnionPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("银联支付 - 银行卡验证通过");
System.out.println("使用银联支付,金额:" + amount);
System.out.println("银联支付成功\n");
}
}
// 3. 上下文角色:管理策略,提供统一调用接口
public class PaymentContext {
// 持有抽象策略的引用
private PaymentStrategy paymentStrategy;
// 构造方法:初始化策略
public PaymentContext(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 动态切换策略
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
// 统一调用策略的支付方法
public void pay(double amount) {
paymentStrategy.pay(amount);
}
}
// 4. 客户端调用(策略模式实现)
public class Client {
public static void main(String[] args) {
// 1. 创建具体策略对象
PaymentStrategy alipay = new AlipayStrategy();
PaymentStrategy wechat = new WechatPayStrategy();
PaymentStrategy unionpay = new UnionPayStrategy();
// 2. 创建上下文对象,传入初始策略(支付宝)
PaymentContext context = new PaymentContext(alipay);
// 调用支付方法
context.pay(100.0);
// 3. 动态切换策略(微信支付)
context.setPaymentStrategy(wechat);
context.pay(200.0);
// 4. 动态切换策略(银联支付)
context.setPaymentStrategy(unionpay);
context.pay(300.0);
// 5. 新增支付方式(如京东支付),无需修改原有代码
PaymentStrategy jdPay = new JdPayStrategy(); // 新增策略类
context.setPaymentStrategy(jdPay);
context.pay(400.0);
}
}
// 新增:京东支付策略(无需修改原有任何代码)
class JdPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("京东支付 - 账户验证通过");
System.out.println("使用京东支付,金额:" + amount);
System.out.println("京东支付成功\n");
}
}
【运行结果】:
text
支付宝支付 - 签名验证通过
使用支付宝支付,金额:100.0
支付宝支付成功
微信支付 - 公众号授权通过
使用微信支付,金额:200.0
微信支付成功
银联支付 - 银行卡验证通过
使用银联支付,金额:300.0
银联支付成功
京东支付 - 账户验证通过
使用京东支付,金额:400.0
京东支付成功
【代码优势极其明显】:
-
解耦彻底:每种支付逻辑封装成独立策略类,无if-else耦合,代码简洁、可读性强;
-
扩展性极强:新增支付方式,只需新增策略类实现抽象接口,无需修改原有代码,符合"开闭原则";
-
复用性高:支付策略可在多个地方复用(如订单支付、退款支付),无需重复编写逻辑;
-
维护成本低:修改某一种支付逻辑,只需修改对应策略类,不影响其他策略和上下文,不易出错;
-
动态切换:客户端可通过上下文的set方法,动态切换支付策略,灵活适配不同业务场景。
【核心总结】:策略模式的核心不是"新增功能",而是"解耦与复用"------通过抽象策略统一接口,将具体算法封装成独立策略类,由上下文管理策略的切换和调用,让客户端与具体算法解耦,从而提升代码的可扩展性、可维护性和复用性。
四、策略模式实战(真实业务场景,可直接复用)
结合Java后端最常见的"后台管理系统 - 接口参数校验"场景,用策略模式实现"不同接口的动态校验",贴合真实项目开发(Spring Boot环境),代码可直接复制到项目中使用,解决实际开发中的if-else校验痛点。
4.1 实战场景说明
场景:后台管理系统有3个核心接口(登录接口、注册接口、修改密码接口),每个接口的参数校验规则不同:
-
登录接口:校验手机号(非空、11位)、密码(非空、6-18位);
-
注册接口:校验手机号(非空、11位)、验证码(非空、6位)、密码(非空、6-18位)、确认密码(与密码一致);
-
修改密码接口:校验原密码(非空)、新密码(非空、6-18位)、确认新密码(与新密码一致)。
要求:用策略模式实现校验逻辑,支持动态切换校验策略,新增接口(如忘记密码)时,无需修改原有校验逻辑,只需新增校验策略,贴合Spring Boot实战规范,支持异常统一处理。
-
抽象策略:校验策略接口(定义校验方法,返回校验结果);
-
具体策略:登录校验策略、注册校验策略、修改密码校验策略;
-
上下文:校验上下文(管理校验策略,提供统一校验接口);
-
实战亮点:结合Spring依赖注入、自定义注解,实现策略的自动识别和调用,贴合真实项目开发。
4.2 实战代码实现(Spring Boot环境,可直接复用)
java
// 1. 公共实体:校验结果封装(统一返回校验状态和提示信息)
@Data
public class ValidationResult {
// 校验是否通过
private boolean success;
// 校验失败提示信息
private String message;
// 静态方法:快速创建成功/失败结果
public static ValidationResult success() {
ValidationResult result = new ValidationResult();
result.setSuccess(true);
return result;
}
public static ValidationResult fail(String message) {
ValidationResult result = new ValidationResult();
result.setSuccess(false);
result.setMessage(message);
return result;
}
}
// 2. 抽象策略角色:校验策略接口
public interface ValidationStrategy {
// 核心方法:参数校验(传入请求参数,返回校验结果)
ValidationResult validate(Map<String, Object> params);
}
// 3. 具体策略角色1:登录接口校验策略
@Component
public class LoginValidationStrategy implements ValidationStrategy {
@Override
public ValidationResult validate(Map<String, Object> params) {
// 1. 校验手机号
String phone = (String) params.get("phone");
if (phone == null || phone.isEmpty()) {
return ValidationResult.fail("手机号不能为空");
}
if (!phone.matches("^1[3-9]\\d{9}$")) {
return ValidationResult.fail("手机号格式不正确(需11位有效号码)");
}
// 2. 校验密码
String password = (String) params.get("password");
if (password == null || password.isEmpty()) {
return ValidationResult.fail("密码不能为空");
}
if (password.length() < 6 || password.length() > 18) {
return ValidationResult.fail("密码长度需在6-18位之间");
}
// 校验通过
return ValidationResult.success();
}
}
// 3. 具体策略角色2:注册接口校验策略
@Component
public class RegisterValidationStrategy implements ValidationStrategy {
@Override
public ValidationResult validate(Map<String, Object> params) {
// 1. 复用手机号校验(可抽取公共方法,此处简化)
String phone = (String) params.get("phone");
if (phone == null || phone.isEmpty()) {
return ValidationResult.fail("手机号不能为空");
}
if (!phone.matches("^1[3-9]\\d{9}$")) {
return ValidationResult.fail("手机号格式不正确(需11位有效号码)");
}
// 2. 校验验证码
String code = (String) params.get("code");
if (code == null || code.isEmpty()) {
return ValidationResult.fail("验证码不能为空");
}
if (!code.matches("^\\d{6}$")) {
return ValidationResult.fail("验证码格式不正确(需6位数字)");
}
// 3. 校验密码
String password = (String) params.get("password");
if (password == null || password.isEmpty()) {
return ValidationResult.fail("密码不能为空");
}
if (password.length() < 6 || password.length() > 18) {
return ValidationResult.fail("密码长度需在6-18位之间");
}
// 4. 校验确认密码
String confirmPassword = (String) params.get("confirmPassword");
if (confirmPassword == null || confirmPassword.isEmpty()) {
return ValidationResult.fail("确认密码不能为空");
}
if (!password.equals(confirmPassword)) {
return ValidationResult.fail("两次密码输入不一致");
}
return ValidationResult.success();
}
}
// 3. 具体策略角色3:修改密码接口校验策略
@Component
public class UpdatePasswordValidationStrategy implements ValidationStrategy {
@Override
public ValidationResult validate(Map<String, Object> params) {
// 1. 校验原密码
String oldPassword = (String) params.get("oldPassword");
if (oldPassword == null || oldPassword.isEmpty()) {
return ValidationResult.fail("原密码不能为空");
}
// 2. 校验新密码
String newPassword = (String) params.get("newPassword");
if (newPassword == null || newPassword.isEmpty()) {
return ValidationResult.fail("新密码不能为空");
}
if (newPassword.length() < 6 || newPassword.length() > 18) {
return ValidationResult.fail("新密码长度需在6-18位之间");
}
// 3. 校验确认新密码
String confirmNewPassword = (String) params.get("confirmNewPassword");
if (confirmNewPassword == null || confirmNewPassword.isEmpty()) {
return ValidationResult.fail("确认新密码不能为空");
}
if (!newPassword.equals(confirmNewPassword)) {
return ValidationResult.fail("两次新密码输入不一致");
}
return ValidationResult.success();
}
}
// 4. 上下文角色:校验上下文(Spring Bean,管理所有校验策略)
@Component
public class ValidationContext {
// 注入所有校验策略(key:策略名称,value:策略对象)
private final Map<String, ValidationStrategy> strategyMap;
// 构造方法注入(Spring自动将所有ValidationStrategy实现类注入到map中)
public ValidationContext(Map<String, ValidationStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
// 统一校验方法:根据策略名称选择策略,执行校验
public ValidationResult validate(String strategyName, Map<String, Object> params) {
// 获取对应策略
ValidationStrategy strategy = strategyMap.get(strategyName);
if (strategy == null) {
return ValidationResult.fail("不支持该接口的校验策略");
}
// 执行校验
return strategy.validate(params);
}
}
// 5. 业务服务层:接口服务(调用校验上下文,实现业务逻辑)
@Service
public class UserService {
private final ValidationContext validationContext;
// 注入校验上下文
public UserService(ValidationContext validationContext) {
this.validationContext = validationContext;
}
// 登录接口
public String login(Map<String, Object> params) {
// 调用校验上下文,使用登录校验策略
ValidationResult result = validationContext.validate("loginValidationStrategy", params);
if (!result.isSuccess()) {
return result.getMessage();
}
// 校验通过,执行登录业务逻辑(此处简化)
return "登录成功";
}
// 注册接口
public String register(Map<String, Object> params) {
ValidationResult result = validationContext.validate("registerValidationStrategy", params);
if (!result.isSuccess()) {
return result.getMessage();
}
// 校验通过,执行注册业务逻辑(此处简化)
return "注册成功";
}
// 修改密码接口
public String updatePassword(Map<String, Object> params) {
ValidationResult result = validationContext.validate("updatePasswordValidationStrategy", params);
if (!result.isSuccess()) {
return result.getMessage();
}
// 校验通过,执行修改密码业务逻辑(此处简化)
return "密码修改成功";
}
}
// 6. 控制层:接口对外提供访问(Spring Boot Controller)
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public String login(@RequestBody Map<String, Object> params) {
return userService.login(params);
}
@PostMapping("/register")
public String register(@RequestBody Map<String, Object> params) {
return userService.register(params);
}
@PostMapping("/updatePassword")
public String updatePassword(@RequestBody Map<String, Object> params) {
return userService.updatePassword(params);
}
}
// 7. 测试类(模拟接口调用,Spring Boot环境可直接注入测试)
public class ValidationTest {
public static void main(String[] args) {
// 模拟Spring容器注入(实际项目中用@Autowired注入)
LoginValidationStrategy loginStrategy = new LoginValidationStrategy();
RegisterValidationStrategy registerStrategy = new RegisterValidationStrategy();
UpdatePasswordValidationStrategy updateStrategy = new UpdatePasswordValidationStrategy();
// 初始化策略map
Map<String, ValidationStrategy> strategyMap = new HashMap<>();
strategyMap.put("loginValidationStrategy", loginStrategy);
strategyMap.put("registerValidationStrategy", registerStrategy);
strategyMap.put("updatePasswordValidationStrategy", updateStrategy);
// 初始化上下文和服务
ValidationContext context = new ValidationContext(strategyMap);
UserService userService = new UserService(context);
// 测试登录接口(参数正确)
Map<String, Object> loginParams = new HashMap<>();
loginParams.put("phone", "13800138000");
loginParams.put("password", "123456");
System.out.println(userService.login(loginParams)); // 输出:登录成功
// 测试登录接口(参数错误:手机号格式不正确)
loginParams.put("phone", "123456");
System.out.println(userService.login(loginParams)); // 输出:手机号格式不正确(需11位有效号码)
// 测试注册接口(参数正确)
Map<String, Object> registerParams = new HashMap<>();
registerParams.put("phone", "13800138000");
registerParams.put("code", "123456");
registerParams.put("password", "123456");
registerParams.put("confirmPassword", "123456");
System.out.println(userService.register(registerParams)); // 输出:注册成功
}
}
【运行结果】:
text
登录成功
手机号格式不正确(需11位有效号码)
注册成功
【实战亮点】:
-
贴合Spring Boot实战:使用@Component、@Service、@RestController等注解,符合真实项目开发规范,可直接复制复用;
-
策略自动注入:通过Spring的Map注入,自动将所有校验策略放入map,无需手动创建和管理策略对象,简化开发;
-
扩展性极强:新增接口(如忘记密码),只需新增校验策略类实现ValidationStrategy接口,无需修改原有上下文和服务代码;
-
代码可维护性高:每种接口的校验逻辑独立,修改某一种校验规则,只需修改对应策略类,不影响其他接口;
-
异常统一处理:通过ValidationResult封装校验结果,统一返回提示信息,贴合接口开发的实际需求。
补充:真实项目中,可结合自定义注解(如@ValidationStrategy("login")),简化策略的调用,无需手动传入策略名称,进一步提升开发效率。
五、策略模式在JDK/框架中的应用(面试必提)
策略模式的核心价值是"算法的封装与切换",这也是它被广泛应用在JDK源码和主流Java框架中的原因,掌握这些应用场景,面试时能加分不少,还能帮助你理解框架底层设计思想。
5.1 JDK 中的策略模式(最常见,面试高频)
JDK中有多个经典的策略模式应用,其中Comparator、ThreadPoolExecutor是面试必问考点,一定要掌握:
5.1.1 Comparator 接口(最典型)
Java中的Comparator接口(比较器),是策略模式的标准实现,用于定义不同的排序策略,客户端可动态切换排序方式:
-
抽象策略(Strategy):Comparator接口(定义了compare(T o1, T o2)方法,是所有排序策略的统一接口);
-
具体策略(ConcreteStrategy):Comparator的各种实现类(如按自然顺序排序、按字符串长度排序、自定义排序);
-
上下文(Context):Collections.sort()方法(接收Comparator对象,调用其compare方法完成排序,动态切换排序策略)。
java
public class ComparatorDemo {
public static void main(String[] args) {
List<String> list = Arrays.asList("java", "strategy", "pattern", "csdn");
// 具体策略1:按字符串自然顺序排序(默认策略)
Collections.sort(list, Comparator.naturalOrder());
System.out.println("自然顺序排序:" + list);
// 具体策略2:按字符串长度排序(切换策略)
Collections.sort(list, Comparator.comparingInt(String::length));
System.out.println("按长度排序:" + list);
// 具体策略3:按字符串逆序排序(自定义策略)
Collections.sort(list, (s1, s2) -> s2.compareTo(s1));
System.out.println("逆序排序:" + list);
}
}
核心逻辑:Comparator接口作为抽象策略,统一了所有排序策略的接口;Collections.sort()作为上下文,接收具体的排序策略,动态切换排序方式,客户端无需关心排序的具体实现,只需传入对应的策略即可。
5.1.2 ThreadPoolExecutor 的拒绝策略
Java线程池ThreadPoolExecutor中,拒绝策略(RejectedExecutionHandler)是策略模式的典型应用,用于处理线程池满时的任务拒绝逻辑:
-
抽象策略(Strategy):RejectedExecutionHandler接口(定义了rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法);
-
具体策略(ConcreteStrategy):4种内置拒绝策略(AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy);
-
上下文(Context):ThreadPoolExecutor(持有拒绝策略引用,线程池满时调用拒绝策略的方法)。
核心逻辑:客户端可在创建线程池时,指定不同的拒绝策略,动态切换线程池满时的处理方式,无需修改线程池的核心逻辑,符合策略模式的设计思想。
5.2 框架中的策略模式
5.2.1 Spring MVC 中的 HandlerAdapter
Spring MVC的HandlerAdapter(处理器适配器),用于适配不同类型的处理器(如Controller、HttpRequestHandler),底层采用策略模式实现:
-
抽象策略(Strategy):HandlerAdapter接口(定义了supports(Object handler)、handle(HttpServletRequest request, HttpServletResponse response, Object handler)方法);
-
具体策略(ConcreteStrategy):RequestMappingHandlerAdapter、HttpRequestHandlerAdapter等(适配不同类型的处理器);
-
上下文(Context):DispatcherServlet(遍历所有HandlerAdapter,找到适配当前处理器的策略,调用其handle方法)。
核心逻辑:通过策略模式,Spring MVC可适配不同类型的处理器,新增处理器类型时,只需新增对应的HandlerAdapter实现类,无需修改DispatcherServlet的核心逻辑,扩展性极强。
5.2.2 MyBatis 中的 Executor
MyBatis的Executor(执行器),用于执行SQL语句,底层采用策略模式实现不同的执行策略:
-
抽象策略(Strategy):Executor接口(定义了query、update等方法,统一执行SQL的接口);
-
具体策略(ConcreteStrategy):SimpleExecutor(简单执行器)、ReuseExecutor(复用预处理语句)、BatchExecutor(批量执行器);
-
上下文(Context):SqlSession(持有Executor引用,动态切换不同的执行策略,执行SQL语句)。
核心逻辑:客户端可通过SqlSession指定不同的Executor策略,动态切换SQL的执行方式(如批量执行、复用语句),无需修改SQL的执行逻辑,提升灵活性。
六、策略模式面试高频考点(必背,避坑)
策略模式是Java后端面试的高频考点(中高级岗位尤为突出),重点考察"核心思想""核心角色""JDK应用""与其他模式的区别",记住以下考点,轻松应对面试。
1. 策略模式的核心作用是什么?(高频)
核心答案(一句话记住,面试直接说):将一组可替换的算法/行为封装成独立的策略类,通过上下文管理策略的动态切换,让客户端与具体算法解耦,提升代码的可扩展性、可维护性和复用性,符合开闭原则。
补充:策略模式解决的是"多种相似算法耦合"的问题,核心是"算法的封装与切换",而非"功能增强"或"结构管理"。
2. 策略模式的核心角色有哪些?(必背)
核心答案(一句话记住):抽象策略(统一接口)、具体策略(实现算法)、上下文(管理策略)、客户端(调用上下文)。
| 角色名称 | 核心职责 | 示例 |
|---|---|---|
| 抽象策略 | 定义所有具体策略的统一接口,声明核心方法,规范策略的实现 | PaymentStrategy、Comparator |
| 具体策略 | 实现抽象策略接口,封装具体的算法/行为,是策略的具体实现 | AlipayStrategy、自然排序Comparator |
| 上下文 | 维护抽象策略引用,提供统一调用接口,负责动态切换策略 | PaymentContext、Collections.sort() |
| 客户端 | 创建具体策略和上下文,通过上下文调用策略,无需直接操作具体策略 | Client类、业务服务层 |
3. 策略模式和模板方法模式的区别?(高频)
很多面试官会把这两个行为型模式放在一起问,核心区别(一句话区分):策略模式是"算法的动态切换",所有策略是平等的,可完全替换;模板方法模式是"固定流程+可变步骤",步骤不可替换,只能修改步骤的实现。
| 对比维度 | 策略模式 | 模板方法模式 |
|---|---|---|
| 核心目的 | 动态切换不同的算法/行为,算法可完全替换 | 定义固定流程,只允许修改流程中的个别步骤实现 |
| 关系 | 策略之间是平等的,无依赖关系,可自由切换 | 父类定义流程,子类继承父类,修改步骤实现,有继承关系 |
| 灵活性 | 灵活性高,可动态切换策略,新增策略无需修改原有代码 | 灵活性低,流程固定,只能修改步骤实现,无法改变流程 |
| 使用场景 | 多种相似算法(如支付方式、排序方式),需动态切换 | 固定流程(如流程审批、数据导入),个别步骤可定制 |
4. 策略模式的优缺点是什么?(必背)
核心答案(简洁好记,面试直接说):
-
优点:解耦(客户端与具体算法分离)、扩展性强(新增策略无需修改原有代码)、复用性高(策略可重复使用)、代码简洁(消除if-else耦合);
-
缺点:策略类数量会增多(每种算法对应一个策略类)、客户端需了解所有策略(才能选择合适的策略)。
5. 如何解决策略模式"策略类过多"的问题?(进阶考点)
核心答案(面试加分):可结合"工厂模式"或"注解+反射"优化,由工厂类统一管理策略的创建和获取,客户端无需直接创建策略对象,只需传入策略标识,由工厂返回对应策略,减少客户端对策略的依赖。
示例:在实战案例中,可新增策略工厂类,根据接口标识(如"login""register"),自动创建并返回对应的校验策略,进一步简化客户端代码。
七、总结(新手必看)
策略模式的核心很简单:将不同的算法/行为分开装,用一个"中间人"(上下文)管理,客户端只需找中间人,无需关心具体算法,新增算法只需新增"包装类",不用改原来的代码。
对于新手来说,掌握策略模式的关键是"区分核心角色"和"理解解耦思想"------先学会用策略模式优化简单的if-else场景(如支付、排序),再结合Spring Boot实战案例,理解策略的注入和动态切换;对于中高级开发者,重点掌握策略模式在框架中的应用,以及与其他模式的区别,面试时才能从容应对。
最后记住:策略模式不是"万能的",它只适用于"多种相似算法/行为需要动态切换"的场景,若没有动态切换的需求,无需强行使用(否则会增加代码复杂度)。合理使用策略模式,能让你的代码更简洁、更易维护、更具扩展性,这也是后端开发者从"会写代码"到"会写好代码"的关键一步。