策略模式详情
策略模式:定义一组算法,将每个算法封装起来,使它们可以互相替换,且算法的变换不会影响使用算法的客户。
• 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
• 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
• 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
🧩 一、问题背景:传统策略模式的痛点
现在有三种促销策略:
//抽象策略(Strategy)类
public interface Strategy {void show();
}
//具体策略(Concrete Strategy)类
public class StrategyA implements Strategy {public void show() { System.out.println("买一送一"); }
}
public class StrategyB implements Strategy {public void show() { System.out.println("满200减50"); }
}
public class StrategyC implements Strategy {public void show() { System.out.println("加1元换购"); }
}
//定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
public class SalesMan { //持有抽象策略角色的引用 private Strategy strategy; public SalesMan(Strategy strategy) { this.strategy = strategy; } //向客户展示促销活动 public void salesManShow(){ strategy.show(); }
}
业务方使用策略时,传统写法是这样👇
public class Client {public static void main(String[] args) {String festival = "Spring"; // 春节SalesMan man = null;if ("Spring".equals(festival)) {man = new SalesMan(new StrategyA());} else if ("MidAutumn".equals(festival)) {man = new SalesMan(new StrategyB());} else if ("Christmas".equals(festival)) {man = new SalesMan(new StrategyC());}man.salesManShow();}
}
🔴 问题:
上层业务(Client)必须写一堆 if-else
来选择策略。
当节日越来越多,这部分就会变得臃肿、难维护。
🧠 二、引入“简单工厂模式”来“接管 if-else”
定义一个“策略工厂”,专门负责根据条件创建策略对象👇
public class StrategyFactory {public static Strategy getStrategy(String festival) {if ("Spring".equals(festival)) {return new StrategyA();} else if ("MidAutumn".equals(festival)) {return new StrategyB();} else if ("Christmas".equals(festival)) {return new StrategyC();}throw new IllegalArgumentException("未知节日:" + festival);}
}
🏭 三、工厂 + 策略模式结合,解放Client
public class Client {public static void main(String[] args) {String festival = "MidAutumn"; // 中秋节// ✅ 不需要自己写 if-else 了,交给工厂选择策略Strategy strategy = StrategyFactory.getStrategy(festival);// ✅ 策略交给 SalesMan 执行SalesMan man = new SalesMan(strategy);man.salesManShow();}
}
💡 四、理解
「结合 if-else + 工厂模式,把具体的实现类对象交给策略模式,让上游业务调用方解放策略的选择。」
逐字拆解理解:
关键词 | 含义 |
---|---|
if-else + 工厂模式 | 工厂内部仍然用 if-else 判断节日类型,但外部业务层不需要管这个判断逻辑了。 |
把具体的实现类对象交给策略模式 | 工厂负责创建正确的策略(StrategyA 、StrategyB 、StrategyC ),然后交给 SalesMan 使用。 |
让上游业务调用方解放策略的选择 | 调用方只需告诉“是什么场景”(如节日名称),不需要自己 new 策略对象,也不需要判断逻辑。 |
🧱 五、总结一:
把“如何选择策略”的逻辑(if-else)封装进“工厂模式”,
把“如何执行策略”的逻辑封装进“策略模式”,
从而让“上游业务调用方”只需传一个简单参数(如节日名),就能自动使用正确的策略。
🔧 六、去掉 if-else :注册工厂模式+策略模式
为了更优雅,我们甚至可以去掉工厂中的 if-else
,用 Map 注册策略。下面举一个生产中二点实例:用注册工厂模式 + 策略模式,解决登录逻辑中 if-else
冗余、扩展性差的问题。
🧩 1. 传统写法
UserService.login()
@Servicepublic class UserService {public LoginResp login(LoginReq loginReq){if ("account".equals(loginReq.getType())) {// 用户名密码登录逻辑} else if ("sms".equals(loginReq.getType())) {// 手机验证码登录逻辑} else if ("we_chat".equals(loginReq.getType())) {// 微信登录逻辑}}}
❌ 主要问题:不符合开闭原则 ,比如:新增 QQ 登录、支付宝登录等要改源码
🧠 2. 改造目标
用 策略模式 封装各种登录方式的差异,
用 工厂方法模式 管理和分配不同的策略,
让UserService
专注调度,不再关心具体实现。
🧱 3. 模式改造思路
✅ 策略模式负责:
定义一系列「登录方式」算法,让它们可以互相替换,而不影响使用方。
抽象策略类:
public interface UserGranter {LoginResp login(LoginReq loginReq);
}
具体策略实现:
@Component
public class AccountGranter implements UserGranter {@Overridepublic LoginResp login(LoginReq loginReq) {System.out.println("账号密码登录");return new LoginResp();}
}@Component
public class SmsGranter implements UserGranter {@Overridepublic LoginResp login(LoginReq loginReq) {System.out.println("短信验证码登录");return new LoginResp();}
}@Component
public class WeChatGranter implements UserGranter {@Overridepublic LoginResp login(LoginReq loginReq) {System.out.println("微信登录");return new LoginResp();}
}
✅ 注册工厂方法模式负责:
把策略的创建、管理逻辑统一封装起来,解耦外部调用。
@Component
public class UserLoginFactory implements ApplicationContextAware {private static final Map<String, UserGranter> granterPool = new ConcurrentHashMap<>();@Autowiredprivate LoginTypeConfig loginTypeConfig;// 读取配置文件信息,并注入Bean@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {loginTypeConfig.getTypes().forEach((k, v) -> {granterPool.put(k, (UserGranter) applicationContext.getBean(v));});}// 根据登录类型返回对应策略public UserGranter getGranter(String grantType) {return granterPool.get(grantType);}
}
✅ 配置化管理(灵活扩展)
application.yml
login:types:account: accountGrantersms: smsGranterwe_chat: weChatGranter
LoginTypeConfig.java
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "login")
public class LoginTypeConfig {private Map<String, String> types;
}
✅ 改造后的 Service
@Service
public class UserService {@Autowiredprivate UserLoginFactory factory;public LoginResp login(LoginReq loginReq) {UserGranter granter = factory.getGranter(loginReq.getType());if (granter == null) {LoginResp resp = new LoginResp();resp.setSuccess(false);return resp;}return granter.login(loginReq);}
}
🌈 4. 改造效果对比
对比项 | 改造前(if-else) | 改造后(工厂+策略) |
---|---|---|
可读性 | ❌ 复杂、混乱 | ✅ 清晰、结构化 |
扩展性 | ❌ 改源码才能新增登录方式 | ✅ 新增类 + 配置即可 |
可维护性 | ❌ 所有逻辑堆在一个方法 | ✅ 各登录方式独立实现 |
复用性 | ❌ 难以单独测试某种登录 | ✅ 可独立注入并复用 |
设计原则 | ❌ 违反开闭原则 | ✅ 完全符合开闭原则 |
依赖关系 | 紧耦合 | 松耦合 |
🚀 6. 扩展新增 QQ 登录
只需要三步 ✅:
① 新增策略类
@Component
public class QQGranter implements UserGranter {@Overridepublic LoginResp login(LoginReq loginReq) {System.out.println("QQ 登录");return new LoginResp();}
}
② 修改配置文件
login:types:account: accountGrantersms: smsGranterwe_chat: weChatGranterqq: qQGranter
③ 不改任何 Java 代码,系统自动支持 QQ 登录 🎉
🌈七、应用
- 订单的支付策略
• 支付宝支付
• 微信支付
• 银行卡支付
• 现金支付 - 解析不同类型excel
• xls格式
• xlsx格式 - 打折促销
• 满300元9折
• 满500元8折
• 满1000元7折 - 物流运费阶梯计算
• 5kg以下
• 5kg-10kg
• 10kg-20kg
• 20kg以上
一句话总结:只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化。
🚀 八、总结二
策略模式封装“行为的变化”,工厂模式封装“对象的创建”,
两者结合可以让Service业务层完全不用管“选哪个策略、怎么 new 策略”,
只需告诉系统“我要处理哪个场景”,系统自动选择并执行正确的行为。