策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户端。

模式结构

策略模式包含三个核心角色:

  1. 策略接口(Strategy):定义所有支持的算法的公共接口
  2. 具体策略(Concrete Strategy):实现了策略接口的具体算法类
  3. 环境上下文(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 {
// 获取所有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. 请求响应对象

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;

// getters and setters
}

// 登录响应
public class LoginResp {
private boolean success;
private String message;
private String token;

// getters and setters
}

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);
}
}

策略模式的优点

  1. 开闭原则:无需修改上下文即可引入新策略
  2. 避免条件判断:消除了大量的条件判断语句
  3. 算法复用:可以在多个上下文中复用策略类
  4. 实现分离:将算法的实现与使用分离

策略模式的缺点

  1. 客户端必须了解策略差异:客户端需要选择合适的策略
  2. 增加对象数量:每个策略都是一个类,可能增加系统对象数量
  3. 通信开销:策略与上下文之间可能需要共享数据,增加通信开销

与其他模式的关系

  • 与状态模式:策略模式更像是一种替代条件判断的方法,而状态模式则是通过改变对象状态来改变行为
  • 与工厂模式:常结合使用,工厂模式负责创建策略对象
  • 与模板方法模式:都是封装算法,但策略使用组合,模板方法使用继承

总结

策略模式通过将算法封装到独立的策略类中,使得它们可以相互替换,让算法的变化独立于使用算法的客户端。这种模式特别适用于有多种算法实现相似功能,且需要在运行时动态选择算法的场景。

在实际开发中,策略模式常与工厂模式结合使用,通过工厂来管理和创建策略对象,进一步降低客户端与具体策略的耦合度,提高系统的灵活性和可维护性。