策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。
模式结构
策略模式包含三个核心角色:
- 策略接口(Strategy):定义所有支持的算法的公共接口
- 具体策略(Concrete Strategy):实现了策略接口的具体算法类
- 环境上下文(Context):持有一个策略对象的引用,提供给客户端使用
应用场景
策略模式适用于以下场景:
- 一个系统需要动态地在几种算法中选择一种时
- 有许多相关的类,仅行为有区别时
- 需要避免使用多重条件判断语句(if-else/switch-case)
- 客户端不需要知道具体算法细节时
完整代码示例:多方式登录系统
下面是一个完整的策略模式实现示例,展示了如何使用策略模式+工厂模式优化多方式登录系统:
1. 策略接口定义
public interface UserGranter {
// 登录接口
LoginResp login(LoginReq loginReq);
// 获取登录类型
LoginType getLoginType();
}
2. 具体策略实现
// 账号密码登录策略
@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. 登录类型枚举
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. 策略工厂
@Component
public class UserLoginFactory implements ApplicationContextAware {
private Map<LoginType, UserGranter> granterPool = new HashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取所有UserGranter接口的实现类
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. 请求响应对象
// 登录请求
public class LoginReq {
private String type; // 登录类型
private String username;
private String password;
private String smsCode;
private String wechatToken;
// getters and setters
}
// 登录响应
public class LoginResp {
private boolean success;
private String message;
private String token;
// getters and setters
}
6. 服务层调用
@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);
}
}
策略模式的优点
- 开闭原则:无需修改上下文即可引入新策略
- 避免条件判断:消除了大量的条件判断语句
- 算法复用:可以在多个上下文中复用策略类
- 实现分离:将算法的实现与使用分离
策略模式的缺点
- 客户端必须了解策略差异:客户端需要选择合适的策略
- 增加对象数量:每个策略都是一个类,可能增加系统对象数量
- 通信开销:策略与上下文之间可能需要共享数据,增加通信开销
与其他模式的关系
- 与状态模式:策略模式更像是一种替代条件判断的方法,而状态模式则是通过改变对象状态来改变行为
- 与工厂模式:常结合使用,工厂模式负责创建策略对象
- 与模板方法模式:都是封装算法,但策略使用组合,模板方法使用继承
总结
策略模式通过将算法封装到独立的策略类中,使得它们可以相互替换,让算法的变化独立于使用算法的客户端。这种模式特别适用于有多种算法实现相似功能,且需要在运行时动态选择算法的场景。
在实际开发中,策略模式常与工厂模式结合使用,通过工厂来管理和创建策略对象,进一步降低客户端与具体策略的耦合度,提高系统的灵活性和可维护性。