策略模式(Strategy Pattern)
策略模式是一种行为设计模式,它将一组行为转换为对象, 并使其在原始上下文对象内部能够相互替换。大白话就是比如我写一个登录业务,目前需要满足能通过系统内、微信等平台进行登录,未来还有可能引入其他的平台,这个时候就可以采用策略模式,来让不同的平台的登录都有对应的策略路径。
此外对于不同类型的交易方式(信用卡、支付宝、微信),生成唯一ID的策略(UUID、雪花算法、Leaf算法)等,我们都可以先用策略模式对其进行行为包装,然后提供给外界进行调用。
一、 策略模式介绍
在策略模式中,主要有两个部分:
- 表示各种策略的对象Strategy
- 行为随着策略对象改变而改变的原始对象Context,它主要用于分发不同策略对象
注意,如果一个系统中的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。下面来看看对应的UML结构图:
Stategy
:抽象策略结构,定义不同策略需要执行的统一步骤和方法ConcreteStrategy1、ConcreteStrategy2
:实现抽象策略定义的接口,提供具体的算法实现Context
:上下文类,是外界获取不同策略的接口
二、策略模式应用
2.1 Java Comparator中的策略模式
在java.util.comparator
中,comparator作为比较的接口,可以实现具体的比较策略。而java.util.Collections
中的sort(List<T> list, Comparator<? super T> c)
作为context类,执行不同的比较逻辑
可以做一个排序的demo来演示:
public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(3);
integers.add(5);
integers.add(4);
integers.add(2);
for (Integer integer : integers) {
System.out.print(integer);
}
System.out.println("顺序后~");
Collections.sort(integers,new AscComparator());
for (Integer integer : integers) {
System.out.print(integer);
}
System.out.println("逆序后~");
Collections.sort(integers,new DescComparator());
for (Integer integer : integers) {
System.out.print(integer);
}
InstantiationStrategy
}
static class DescComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}
static class AscComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
最后输出:
13542顺序后~
54321逆序后~
12345
2.2 Spring Bean实例化中的策略模式
其中InstantiationStrategy
作为实例化策略接口,AbstractAutowireCapableBeanFactory
作为上下文,创建策略并调用
三、 策略模式实战
3.1 生成不同的ID策略
就拿生成唯一ID业务来举例子,比如在雪花算法提出之前,我们一般使用的是UUID 来确认唯一ID。但是如果需要有序的生成ID,这个时候就要考虑一下其他的生成方法,比如雪花、Leaf等算法了。
可能刚开始我们是直接写一个类,在类里面调用UUID算法来生成,但是需要调用其他方法时,我们就必须在这个类里面用if-else
等逻辑判断,然后再转换成另外的算法中。这样的做法和前面提到的工厂模式一样,会提高类之间的耦合度。所以我们可以使用策略模式将这些策略抽离出来,单独实现,防止后期若需要扩展带来的混乱。
首先,定义一个ID生成的接口IIdGenerator
public interface IIdGenerator {
/**
* 获取ID, 目前有三种实现方式
* 1.雪花算法,主要用于生成单号
* 2.日期算法,用于生成活动标号类,特性是生成数字串较短,但是指定时间内不能生成太多
* 3.随机算法,用于生成策略ID
* @return ID 返回ID
*/
long nextId();
}
让不同生成ID策略实现该接口:
下面是雪花算法的具体实现 :
public class SnowFlake implements IIdGenerator {
private Snowflake snowflake;
@PostConstruct
public void init() {
//总共有5位,部署0~32台机器
long workerId;
try {
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
} catch (Exception e) {
workerId = NetUtil.getLocalhostStr().hashCode();
}
workerId = workerId >> 16 & 31;
long dataCenterId = 1L;
snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
}
@Override
public long nextId() {
return snowflake.nextId();
}
}
其次还要定义一个ID策略控制类IdContext
,通过外部不同的策略,利用统一的方法执行ID策略计算,如下所示:
@Configuration
public class IdContext {
@Bean
public Map<Constants.Ids, IIdGenerator> idGenerator(SnowFlake snowFlake, ShortCode shortCode, RandomNumeric randomNumeric) {
Map<Constants.Ids, IIdGenerator> idGeneratorMap = new HashMap<>(8);
idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);
idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);
idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);
return idGeneratorMap;
}
}
所以在最后测试时,直接调用idGeneratorMap
就可以实现不同策略服务的调用:
@Test
public void init() {
logger.info("雪花算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.SnowFlake).nextId());
logger.info("日期算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.ShortCode).nextId());
logger.info("随机算法策略,生成ID: {}", idGeneratorMap.get(Constants.Ids.RandomNumeric).nextId());
}
- 2 实现不同平台登录系统
正如前言提到的,在同样的登录过程中,需要实现不同平台的登录策略,这里就单列出微信登录系统的实现逻辑来展示策略模式:
- 登录接口
该部分提供给前端进行调用,通过前台传递不同的平台参数,来执行不同的登录策略:
public Result<JSONObject> login(@RequestBody LoginRequestModel loginRequest) {
Result<JSONObject> result = new Result<JSONObject>();
if (loginRequest.getThirdPlatform() == null) {
result.error500("找不到该平台,请配置后再登录!");
return result;
} else {
LoginStrategy loginStrategy = loginStrategyContext.getLoginStrategy(loginRequest.getThirdPlatform());
result = loginStrategy.login(loginRequest);
return result;
}
}
- 登录策略context
该部分主要通过创建登录策略来进行调用, 这里是利用spring来将不同策略对象提前注入,方便管理和调用
@Component
public class LoginStrategyContext{
private Map<String, LoginStrategy> strategies = new ConcurrentHashMap<>();
/**
* 将所有策略注入springBean中
* @param strategies
*/
public LoginStrategyContext(Map<String, LoginStrategy> strategies) {
strategies.forEach(this.strategies::put);
}
public LoginStrategy getLoginStrategy(String strategyName) {
LoginStrategy loginStrategy = strategies.get(strategyName);
return loginStrategy;
}
}
- 策略接口和具体实现
该部分完成登录策略接口和具体的登录实现策略
public interface LoginStrategy {
/**
* 处理具体登录逻辑
* @return
*/
Result<JSONObject> login(LoginRequestModel loginRequest);
}
public class WeChatLoginStrategy implements LoginStrategy {
@Override
public Result<JSONObject> login(LoginRequestModel loginRequest) {
//处理具体的登录逻辑
}
}
以上就是关于策略设计模式的内容,其实在日常业务逻辑中对于设计模式的使用,并不是非得一定要代码中有设计模式才行,简单的逻辑就用if-else
即可。如果有复杂的业务逻辑,而且也符合对应的设计模式,这样使用模式才能真正提高代码的逻辑性和可扩展性。