这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
今天周末,明天周一,也算是一个时间节点,把本月未写完的设计模式,最后用一个大家熟悉且常用的模式来做个结尾。网上的策略模式遍地就是,但本文是以解决实际问题为基准。
Q : 面试官问:你工作过程中都用过什么设计模式
A : 策略模式,还是策略模式
今天我将用最真实的案例来运用下,让你以后在工作过程中百试不爽!
策略模式
官方是这样定义的:定义一系列算法,封装每个算法,并使它们可以互换。策略让算法独立于使用它的客户端而变化。
还是老规矩 我们先看图,讲解,最后代码演练!!!
我们下面看副图:
从该图中,我们能看出策略模式包含了三个关键角色
-
上下文信息类(Context):用于存放和执行需要使用的具体策略类以及客户端调用的逻辑
-
抽象策略类(Strategy):定义策略的共有方法,所有的策略都共同拥有的函数
-
具体策略类(StrategyA 等):实现抽象策略类定义的共有方法。
这里注意,策略类,只能有一个类(一般是接口),具体策略类是多个,有几种策略就有多少策略类。
我们先看段简单代码(这里照顾下不了解策略模式的伙伴)
代码演示
//上下文信息类
//用于存放和执行需要使用的具体策略类以及客户端调用的逻
public class Context {
public void request(Strategy s) {
s.operation();
}
}
//策略接口
//定义策略的共有方法
public interface Strategy {
void operation();
}
//具体策略类A 实现策略类
public class StrategyA implements Strategy {
@Override
public void operation() {
System.out.println("=== 执行策略 A ......");
}
}
//具体策略类B 实现策略类
public class StrategyB implements Strategy {
@Override
public void operation() {
System.out.println("=== 执行策略 B ......");
}
}
从上面的代码实现我们能看出,策略模式的本质就是通过上下文信息类来作为中心控制单元,对不同的策略进行调度分配。
我们来下实际场景,最多的可能就是营销场景。
现在要做一个平台会员。平台会员主要做的就是权益,比如我购买来平台会员,会有以下权益
1、赠送平台优惠券
2、赠送观影券
3、赠送积分
.......
那么这些可以理解成策略,我们的策略在我个人看来可以分为两大种
- 指定策略。就是我每次请求指定一种策略模式,比如我们的分享功能。每次只能分享一种
- 全部策略。我们上述的会员权益,用户购买完平台会员,都得把这些权益发送到用户。
我们先拿平台会员场景来演示下(此案例为真实案例,请看完记得点赞收藏):
首先,我们来定义策略 API——PlatformApi,每一种会员权益策略的算法都要实现该接口。该接口有一个 getPlatformType 方法,接收并返回一个 byte类型的会员权益类型。另外一个 createPlatformUser 发送会员权益。
/**
* 平台会员权益API
* @Date 2021/08/22 4:09 下午
* @Author yn
*/
public interface IPlatformAPI {
/**
* 权益类型
* 1、赠送平台优惠券
* 2、赠送观影券
* 3、赠送积分
* 4、赠送视频会员
* 5、和美团合作,赠送美团会员
* @return
*/
byte getPlatformType();
/**
* 创建平台会员用户权益(发送权益)
* @param userId 给谁发送会员权益
* @return
*/
void createPlatformUser(Integer userId);
}
接下来我们定义策略实现类。
/**
* 发送优惠券
* @Date 2021/08/22 6:05 下午
* @Author yn
*/
@Service
public class PlatformCouponAPI implements IPlatformAPI {
@Override
public byte getPlatformType() {
//赠送平台会员券
return 1;
}
@Override
public void createPlatformUser(Integer userId) {
//执行发送优惠券的逻辑
}
}
/**
* 电影券
* @Date 2021/08/22 6:05 下午
* @Author yn
*/
@Service
public class PlatformMmovieCouponAPI implements IPlatformAPI {
@Override
public byte getPlatformType() {
//发送观影券
return 2;
}
@Override
public void createPlatformUser(Integer userId) {
//执行发送观影券的逻辑
}
}
/**
* 赠送积分
* @Date 2021/08/22 6:05 下午
* @Author yn
*/
@Service
public class PlatformIntegralAPI implements IPlatformAPI {
@Override
public byte getPlatformType() {
//赠送积分
return 3;
}
@Override
public void createPlatformUser(Integer userId) {
//执行赠送积分的逻辑
}
}
用户购买完成之后 我们来给用户发送权益
/**
* 模拟调用下
* @Date 2021/08/22 4:50 下午
* @Author yn
*/
@Service
public class PlatformAPI {
@Autowired
private IPlatformAPI api;
public void sendPlatformCard(){
api.createPlatformUser(12345);
}
}
//看到这里的小伙伴应该可以看出 上述方法是行不通的。
// 1、首先没有指定类型,怎么找到指定的bean(策略执行类)
// 2、这里有一个重要的问题,就是怎么根据类型,让Spring扫描到指定的Bean
// 3 我们都知道 正因为加了@service,Spring才能扫描进去 我们的Controller才能正常调用
// 4 这里我们通过一种另外的方式强制扫描bean,动态调用
首先定义一个 PlatformStrategy 策略集合类,让业务类型和类都装到map里面,一一对应起来。我们去加载bean的时候。直接从这里取
/**
* 策略集合类
* @Date 2021/08/22 3:47 下午
* @Author yn
*/
public class PlatformStrategy {
//这里用ConcurrentHashMap 防止并发造成的不可预知错误
private static Map<Byte, IPlatformAPI> map = new ConcurrentHashMap<>(16);
//装类型
public IPlatformAPI getPlatformStrategy(byte type) {
return map.get(type);
}
//将类型和类装进去
public IPlatformAPI setPlatformStrategy(byte type, IPlatformAPI iPlatformAPI) {
return map.put(type, iPlatformAPI);
}
}
到这里我们想,PlatformStrategy该类什么也没有 怎么会被spring扫描进去 我们怎么调用呢。别慌。看下面
//我们都知道。加这两个注解,会直接将bean初始化
//身为配置类,@Configuration
// 又可以理解为组件 @Component
@Component
@Configuration
public class PlatformStrategyConfiguration {
@Bean //必须添加bean注解
public PlatformStrategy setStrategyList(List<IPlatformAPI> iPlatformAPIS) {
//iPlatformAPIS 所有的策略实现类
PlatformStrategy platformStrategy = new PlatformStrategy();
//将所有的类装进去
iPlatformAPIS.forEach(iPlatformAPI -> {
platformStrategy.setPlatformStrategy(iPlatformAPI.getPlatformType(), iPlatformAPI);
});
return platformStrategy;
}
}
OK。到这里 我们的底层配置工作就完成了,我们将上述错误代码改造下
//模拟调用
@Service
public class PlatformAPI {
//将该集合策略类引进来
@Autowired
private PlatformStrategy platformStrategy;
/**
* 根据类型 调用发送权益
* @param list 类型集合
* @param userId 用户 给谁发送权益
*/
public void sendPlatformCard(List<ThsPlatFormCardConfigInfo> list, Integer userId){
//循环发送
list.forEach(s ->{
//根据权益查出bean(策略类)
IPlatformAPI iPlatformAPI = platformStrategy.getPlatformStrategy(s.getCardType());
//真正调用发送
iPlatformAPI.createPlatformUser(userId);
});
}
}
这里注意 上面代码我们的参数 List < ThsPlatFormCardConfigInfo > list 这里可以根据数据库配置,比如我们今天我们要快速下架一项权益。那么可以根据数据库直接配置。做到不用代码。
OK 代码部分到这里就结束了
下面我们对今天的策略模式做下总结
总结
我们简单总结策略模式的优点,今天的重点,是我们上面的这套策略模式的运用。
-
第一个,提升代码的可维护性,提供良好的代码扩展性。 在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过 if-else 等条件判断语句来进行选择非常方便
-
第二个,为了动态快速地替换更多的算法。 从上面的分析你会发现,策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑
-
第三个,为了应对需要频繁更换策略的场景。像我们上面所说的权益配置表
-
降低使用多重条件(if-else)嵌套语句的理解难度
策略模式最大的用处是能在运行时改变代码的算法行为,同时给使用者提供一种可以根据情况来选择算法的途径。
弦外之音
设计模式到今天就结束了,后面我会持续输出其他专栏。感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。
我已经将本章收录在专题里,点击下方专题,关注专栏。
后面我会再出一篇利用多种设计模式的实践。
加油! 我们下期再见!
历史干货