Hello!~大家好啊,很高兴我们又见面了,今天我们一起学习设计模式–【策略模式】
初次对此模式不懂的,或者想偷懒的,我强烈建议大家跟着我的一起把概念和代码一起敲一遍!~为啥子??因为我就是这样学会的,哈哈哈!
UML
图
selector
:选择器又叫做上下文context
,【作用】通过不同的标识从而获取对应的策略实例(这是很关键的一个点,你细品~)抽象策略: 如图所示,这是策略模式的顶层接口【就相当于领导~干运筹帷幄的】
策略实例 n:封装不同算法业务的实例对象,【就是特么真正干活的~!比如俺们码农】
我们一个实际的案例进行实操,掌握这看起来高大上的策略模式,也是后面若你要面试,也能吹牛逼啊~
案例:
比如我们要统计餐厅的人流量数据做统计,统计的时间维度:一周和一个月,我们用策略模式咋实现呢? 我们往下看
- 前置设计:
- 我们定义常量标识策略的类型,
- 使用者调用时可通过常量获取对应的策略实例
- 策略设计
- 策略实例: 创建两个
Bean
实例: 周维度 &&月维度,并实现其中对应的算法和业务- 抽象策略:通常我们定义一个通用的接口或者抽象方法,为啥???为了让外部能够访问啊!否则不是闭关锁国了嘛!~~
- 选择器设计: 这块就是挺重要的啦,我们通常采用
MAP
来存储数据,这样调用者就可以通过策略标识获取对应的策略实例注意:
对于第三点可以是一道面试题:请问你怎么将策略的
Bean
注入到MAP
中的?(不看答案,别心里好家伙,思考思考~哈哈)也许你的第一反应:
我就直接将
Bean
对应的Class
写死在``MAP中,然后通过
applicationContext.getBaen()`获取其实例,这是最简单粗暴的!(也一看就是**菜鸡写法**哈哈哈)若是这样回答,面试官肯定会问,这个通过写死的方法,那么下次我想增加其他维度的那不是要再次往Map中再添加,这麻烦先不说,也不符合开闭原则啊!~
你尴尬的扣了扣脚,微笑回答到:
是的,这是我一开始使用的,当时业务量不大,只考虑上面的两种情况,我就直接使用采取这种直接写死法,
但后面随着业务线的拓展,我也发现此中写法的弊端:
我的改进措施:(在
Springboot
的环境中)1.通过解析注解来实现,给每个策略实例打上一个注解,我们业务是以周和月为维度进行常量标识的
2.过程: 在
Springboot
启动时,通过扩展点扫描抽象策略,获取它的策略实例3.然后解析注解,放入
MAP
中这种就不用对原先代码就行逻辑代码改动,既符合开闭原则,又回到了面试官,爽哉!~
说了这么多,那关于代码我们怎么实现呢?–>耐心点,我们一起往下看
package org.boyunv.strategy_pattern.handler;
/**
*
* 描述: 抽象策略
*
* 需求信息: 【需求ID与需求标题】【客户名称】
*
* @author aristo
* @date 2024/1/23 19:07
*/
public interface TimeDimensionBaseHandlerInterface {
//此方法是留给不同策略实体实现不同策略的方法实现
void statistic();
}
这里我们注册组件直接采用Spring框架的组件
@Component
进行注册即可
1.周统计维度
package org.boyunv.strategy_pattern.handler;
import org.springframework.stereotype.Component;
/**
*
* 描述: 策略实体: 周统计维度$
*
* 需求信息: 【需求ID与需求标题】【客户名称】
*
* @author aristo
* @date 2024/1/23 19:12
*/
@Component("week")
public class StatisticByWeekHandler implements TimeDimensionBaseHandlerInterface{
@Override
public void statistic() {
//具体实现
System.out.println("通过周统计维度实现统计数据");
}
}
2.月统计维度
package org.boyunv.strategy_pattern.handler;
import org.springframework.stereotype.Component;
/**
*
* 描述: 策略实体: 月统计维度$
*
* 需求信息: 【需求ID与需求标题】【客户名称】
*
* @author aristo
* @date 2024/1/23 19:12
*/
@Component("month")
public class StatisticByMonthHandler implements TimeDimensionBaseHandlerInterface{
@Override
public void statistic() {
System.out.println("通过月统计维度实现统计数据");
}
}
看如下的代码,我们会发现他同样是个组件,
@Component
@Resource
:将我们注册的week
和month
组件注册进入MAP
中
package org.boyunv.strategy_pattern.selector;
import org.boyunv.strategy_pattern.handler.TimeDimensionBaseHandlerInterface;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 描述: 选择器,核心重点$
* 需求信息: 【需求ID与需求标题】【客户名称】
* @date 2024/1/23 19:26
*/
@Component
public class StatisticSelector {
//这就和我们上面的所说的:定义Map,并通过@Resource注解完成我们刚才week和month的组件注入(很关键的一步哦!~)
@Resource
private Map<String, TimeDimensionBaseHandlerInterface> selectorMap;
//下面根据类型选择策略
public TimeDimensionBaseHandlerInterface select(String type){
return selectorMap.get(type);
}
}
4.我们进行测试
package org.boyunv.strategy_pattern.selector;
import org.boyunv.strategy_pattern.handler.TimeDimensionBaseHandlerInterface;
import org.junit.Test;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 描述: 选择器,核心重点$
* 需求信息: 【需求ID与需求标题】【客户名称】
* @date 2024/1/23 19:26
*/
@Component
public class StatisticSelector {
//这就和我们上面的所说的:定义Map,并通过@Resource注解完成我们刚才week和month的组件注入(很关键的一步哦!~)
@Resource
private Map<String, TimeDimensionBaseHandlerInterface> selectorMap;
//下面根据类型选择策略
public TimeDimensionBaseHandlerInterface select(String type){
return selectorMap.get(type);
}
public void doInvoke(String type){
TimeDimensionBaseHandlerInterface handler=select(type);
handler.doStatistic();
}
@Test
public void invoke(){
this.doInvoke("week");
this.doInvoke("month");
}
}
通过上面的运行步骤,我们最中实现策略模式的周和月的策略实现
这里面的核心点,再次提醒下:
通过
Spring
获取接口的实现,并解析实现类上的注解的方式,可以在程序启动时动态的将策略注入到一个Map
中,作为策略的容器。使用时传入
标识符
(就是常量)就可以获取到对应的策略执行了。