策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。
模式结构
策略模式包含三个核心角色:
- 策略接口(Strategy):定义所有支持的算法的公共接口
- 具体策略(Concrete Strategy):实现了策略接口的具体算法类
- 环境上下文(Context):持有一个策略对象的引用,提供给客户端使用
应用场景
策略模式适用于以下场景:
- 一个系统需要动态地在几种算法中选择一种时
- 有许多相关的类,仅行为有区别时
- 需要避免使用多重条件判断语句(if-else/switch-case)
- 客户端不需要知道具体算法细节时
完整代码示例:多方式登录系统
下面是一个完整的策略模式实现示例,展示了如何使用策略模式+工厂模式优化多方式登录系统:
1. 策略接口定义
1 2 3 4 5 6 7
| public interface UserGranter { LoginResp login(LoginReq loginReq); LoginType getLoginType(); }
|
2. 具体策略实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| @Component public class AccountGranter implements UserGranter { @Autowired private UserService userService; @Override public LoginResp login(LoginReq loginReq) { LoginResp loginResp = new LoginResp(); if ("admin".equals(loginReq.getUsername()) && "123456".equals(loginReq.getPassword())) { loginResp.setSuccess(true); loginResp.setMessage("账号密码登录成功"); } else { loginResp.setSuccess(false); loginResp.setMessage("账号或密码错误"); } return loginResp; } @Override public LoginType getLoginType() { return LoginType.PASSWORD; } }
@Component public class SmsGranter implements UserGranter { @Override public LoginResp login(LoginReq loginReq) { LoginResp loginResp = new LoginResp(); if ("123456".equals(loginReq.getSmsCode())) { loginResp.setSuccess(true); loginResp.setMessage("短信登录成功"); } else { loginResp.setSuccess(false); loginResp.setMessage("验证码错误"); } return loginResp; } @Override public LoginType getLoginType() { return LoginType.SMS; } }
@Component public class WeChatGranter implements UserGranter { @Override public LoginResp login(LoginReq loginReq) { LoginResp loginResp = new LoginResp(); if ("wechat_token_123".equals(loginReq.getWechatToken())) { loginResp.setSuccess(true); loginResp.setMessage("微信登录成功"); } else { loginResp.setSuccess(false); loginResp.setMessage("微信登录失败"); } return loginResp; } @Override public LoginType getLoginType() { return LoginType.WECHAT; } }
|
3. 登录类型枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public enum LoginType { PASSWORD("password", "账号密码登录"), SMS("sms", "短信登录"), WECHAT("wechat", "微信登录"); private String code; private String description; LoginType(String code, String description) { this.code = code; this.description = description; } public String getCode() { return code; } public static LoginType getByCode(String code) { for (LoginType type : values()) { if (type.getCode().equals(code)) { return type; } } return null; } }
|
4. 策略工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Component public class UserLoginFactory implements ApplicationContextAware { private Map<LoginType, UserGranter> granterPool = new HashMap<>(); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, UserGranter> granterMap = applicationContext.getBeansOfType(UserGranter.class); granterMap.forEach((key, granter) -> { granterPool.put(granter.getLoginType(), granter); }); } public UserGranter getGranter(LoginType loginType) { return granterPool.get(loginType); } public UserGranter getGranter(String loginTypeCode) { LoginType loginType = LoginType.getByCode(loginTypeCode); return loginType != null ? granterPool.get(loginType) : null; } }
|
5. 请求响应对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class LoginReq { private String type; private String username; private String password; private String smsCode; private String wechatToken; }
public class LoginResp { private boolean success; private String message; private String token; }
|
6. 服务层调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Service public class UserService { @Autowired private UserLoginFactory factory; public LoginResp login(LoginReq loginReq) { UserGranter granter = factory.getGranter(loginReq.getType()); if (granter == null) { LoginResp loginResp = new LoginResp(); loginResp.setSuccess(false); loginResp.setMessage("不支持的登录方式"); return loginResp; } return granter.login(loginReq); } }
|
策略模式的优点
- 开闭原则:无需修改上下文即可引入新策略
- 避免条件判断:消除了大量的条件判断语句
- 算法复用:可以在多个上下文中复用策略类
- 实现分离:将算法的实现与使用分离
策略模式的缺点
- 客户端必须了解策略差异:客户端需要选择合适的策略
- 增加对象数量:每个策略都是一个类,可能增加系统对象数量
- 通信开销:策略与上下文之间可能需要共享数据,增加通信开销
与其他模式的关系
- 与状态模式:策略模式更像是一种替代条件判断的方法,而状态模式则是通过改变对象状态来改变行为
- 与工厂模式:常结合使用,工厂模式负责创建策略对象
- 与模板方法模式:都是封装算法,但策略使用组合,模板方法使用继承
总结
策略模式通过将算法封装到独立的策略类中,使得它们可以相互替换,让算法的变化独立于使用算法的客户端。这种模式特别适用于有多种算法实现相似功能,且需要在运行时动态选择算法的场景。
在实际开发中,策略模式常与工厂模式结合使用,通过工厂来管理和创建策略对象,进一步降低客户端与具体策略的耦合度,提高系统的灵活性和可维护性。