简介
策略模式(Strategy Pattern)是一种软件设计模式,属于行为型设计模式。
一般的,如果程序中存在对同一个场景做不同的业务处理实现的时候可以考虑使用策略模式。
使用策略模式编排代码可以解决 if-else 带来的代码复杂度高、易读性差,难以维护的问题。
优点:
1、业务实现算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点:
1、代码量增多。
2、所有策略类都需要对外暴露。
3、当策略过多的时候会引发策略膨胀的问题。
类图
业务场景以加减乘运算为背景:
- 定义了一个业务处理接口 Strategy ,其中定义了一个 doOperation 的方法,此方法接收 int 类型的参数。
- 编写了三个业务处理实现类,分别重写 doOperation 方法来实施具体的运算逻辑。
- 定义一个策略上下文类 Context ,存放所有的策略,并定义一个executeStrategy 方法用来执行某一个策略。
- 在程序 main 方法中调用 策略上下文类实现程序功能。
- 具体的业务处理实现类可以根据需要删除、增加,在 策略上下文中维护可用的策略有哪些
举例
以SpringBoot项目举例如何在代码中编写策略模式
业务描述
业务背景为一个视频监控系统,该系统可实现对摄像头的实时预览、云台操作等功能。该系统可以集成多个厂家的摄像头,如海康、大华、天地伟业等。
业务流程为:
- 摄像头列表页面展示所有摄像头
- 点击列表页的 播放 按钮,查询出该摄像头的实时预览 rtsp-url
- 需要根据摄像头唯一标识 cameraId 查询出该摄像头的类型,再根据不同的类型调用不同的业务实现类获取预览的 rtsp-url
代码实现
定义操作摄像机接口
public interface CameraActionStrategy {
String getRtspUrl(String cameraId);
}
- 该接口有一个获取rtspUrl 的方法,入参为摄像机唯一标识
编写操作摄像机实现
@Slf4j
@Service
public class HikvisionCameraService implements CameraActionStrategy {
@Override
public String getRtspUrl(String cameraId){
//参数校验
//调用海康 openApi 获取视频预览url
//异常处理和资源释放
return "rtsp://hikvision/1/1";
}
}
@Slf4j
@Service
public class DahuaCameraService implements CameraActionStrategy {
@Override
public String getRtspUrl(String cameraId){
//参数校验
//调用大华 openApi 获取视频预览url
//异常处理和资源释放
return "rtsp://dahua/1/1";
}
}
@Slf4j
@Service
public class TiandyCameraService implements CameraActionStrategy {
@Override
public String getRtspUrl(String cameraId){
//参数校验
//调用天地伟业 openApi 获取视频预览url
//异常处理和资源释放
return "rtsp://tiandy/1/1";
}
}
- 每一个实现类均实现 getRtspUrl 方法,并编写自己特有的获取预览视频的代码逻辑,例如这里可能要根据各个设备厂商的集成规范对接 openApi 平台,甚至可能是根据SDK直连设备。
- 返回一个可用的 rtsp-url 播放地址
- 这种业务场景涉及对接,需要考虑超时如何处理
编写策略上下文
@AllArgsConstructor
@Component
public class CameraActionStrategyContext{
private CameraMapper cameraMapper;
private final Map<String, CameraActionStrategy> strategyMap = new ConcurrentHashMap<>();
public CameraActionStrategy getPreviewStrategy(String cameraId) {
if (StringUtils.isEmpty(cameraId)) {
return null;
}
//获取相机类型
Camera camera = cameraMapper.getCameraDetail(cameraId);
//数据校验
String strategyServiceName = CameraStrategyTypeEnum.getServiceName(camera.getType);
return strategyMap.get(strategyServiceName);
}
}
@Getter
@AllArgsConstructor
enum CameraStrategyTypeEnum {
HIKVISION("hikvision", "hikvisionCameraService", "海康"),
DAHUA("dahua", "dahuaCameraService", "大华"),
TIANDY("tiandy", "tiandyCameraService", "天地伟业"),
;
private final String cameraType;
private final String strategyServiceName;
private final String describe;
/**
* 获取策略实现service - 根据 cameraType
*
* @param cameraType
* @return
*/
public static String getServiceName(String cameraType) {
if (StringUtils.isEmpty(cameraType)) {
return null;
}
String v = null;
for (CameraStrategyTypeEnum i : CameraStrategyTypeEnum.values()) {
if (i.getCameraId() != null && i.getCameraId().equals(cameraType)) {
v = i.getStrategy();
break;
}
}
return v;
}
}
- 策略上下文类需要注入到容器中,这样 bizService 可以直接注入并调用上下文从而在运行时获取不同的策略
- 注入了一个 CameraMapper ,用来根据 id 获取 type
- 因为添加了 @AllArgsConstructor 注解,所以当此类实例化的时候,会通过构造函数注入的方式获取 CameraActionStrategy 接口的所有实现类,并存放到局部变量 strategyMap 中,key为实现类的名字,若不加别名默认就是类名全拼(首字母小写)。value为策略实现类实例,有了类实例就可以直接访问类中的方法了
- 编写一个方法获取预览视频策略实现类,如果有多种不同业务场景可定义不同的获取XX策略实现类的方法
- 上下文类中添加一个内部枚举,维护CameraType 和策略实现类名称之间的关系,这样就可以根据 cameraId 找到策略实现类了,请求发起方无需关心摄像头是什么厂商的,只管发起请求就可以返回预览url了
编写播放按钮处理逻辑
@Slf4j
@AllArgsConstructor
@Service
public class CameraService {
private CameraActionStrategyContext cameraActionStrategyContext;
public String getPreviewUrl(String cameraId){
//参数校验
//数据校验: camera 是否存在,是否可用等
CameraActionStrategy strategy = cameraActionStrategyContext.getPreviewStrategy(cameraId);
return strategy.getRtspUrl(cameraId);
}
}