工作中常用的设计模式--策略模式

news/2024/5/10 20:53:36/文章来源:https://blog.csdn.net/BASK2312/article/details/128052561

一般做业务开发,不太容易有大量使用设计模式的场景。这里总结一下在业务开发中使用较为频繁的设计模式。当然语言为Java,基于Spring框架。

1 策略模式(Strategy Pattern)

一个类的行为或方法,在运行时可以根据条件的不同,有不同的策略(行为、方法)去执行。举个简单的例子:去上班,可以骑共享单车、可以选择公交车、也可以乘坐地铁。这里的乘坐什么交通工具就是针对去上班这个行为的策略(解决方案)

策略模式一般有3个角色:

  • Context: 策略的上下文执行环境
  • Strategy: 策略的抽象
  • ConcreteStrategy: 策略的具体实现

这个出现的场景其实还很多。如之前做商城时遇到的登录(手机号、微信、QQ等),及优惠券(满减券、代金券、折扣券等)。这里主要讲一下最近遇到的两种。一种是预先知道要走哪个策略,一种是需要动态计算才能确定走哪种策略。

1.1 静态(参数)策略

在做增长系统时,用户留资进线需要根据不同来源走不同的处理逻辑。而这种来源,在数据出现时就能确定。

SyncContext

/*** 同步上下文**/
@Data
@Builder
public class SyncContext {// 任务IDprivate Long taskId;// 任务类型 1: 自然注册; 2: 团购用户; 3: 落地页留资private Integer taskType;// 所有留资相关信息(忽略细节)private Object reqVO;// 存储执行策略名称(伪装执行结果)private String respVO;
}
复制代码

SyncStrategy

/*** 同步策略**/
public interface SyncStrategy {/*** 具体策略* @param ctx Context*/void process(SyncContext ctx);
}
复制代码

OtSyncStrategy

/*** 自然注册**/
@Slf4j
@Service
public class OtSyncStrategy implements SyncStrategy, BeanNameAware {private String beanName;@Overridepublic void process(SyncContext ctx) {log.info("[自然注册] {}", ctx);ctx.setRespVO(beanName);}@Overridepublic void setBeanName(String s) {beanName = s;}
}
复制代码

AbSyncStrategy

/*** 团购用户**/
@Slf4j
@Service
public class AbSyncStrategy implements SyncStrategy, BeanNameAware {private String beanName;@Overridepublic void process(SyncContext ctx) {log.info("[团购用户] {}", ctx);ctx.setRespVO(beanName);}@Overridepublic void setBeanName(String s) {beanName = s;}
}
复制代码

DefaultSyncStrategy

/*** 落地页注册(Default)**/
@Slf4j
@Service
public class DefaultSyncStrategy implements SyncStrategy, BeanNameAware {private String beanName;@Overridepublic void process(SyncContext ctx) {log.info("[落地页注册] {}", ctx);ctx.setRespVO(beanName);}@Overridepublic void setBeanName(String s) {beanName = s;}
}
复制代码

至此,策略模式的三个角色已凑齐。但似乎还有一些问题,SyncContext中有taskType,但是该怎么与具体的策略匹配呢?我们可以借助Spring框架的依赖注入管理策略。

SyncStrategy

/*** 同步策略**/
public interface SyncStrategy {String OT_STRATEGY = "otStrategy";String AB_STRATEGY = "abStrategy";String DEFAULT_STRATEGY = "defaultStrategy";/*** 具体策略* @param ctx Context*/void process(SyncContext ctx);
}
复制代码

同时修改一下具体策略,指定@Service别名。将3个具体策略类修改完即可。

OtSyncStrategy

/*** 自然注册**/
@Slf4j
@Service(SyncStrategy.OT_STRATEGY)
public class OtSyncStrategy implements SyncStrategy, BeanNameAware {private String beanName;@Overridepublic void process(SyncContext ctx) {log.info("[自然注册] {}", ctx);ctx.setRespVO(beanName);}@Overridepublic void setBeanName(String s) {beanName = s;}
}
复制代码

此时我们似乎还需要一个整合调用的类,否则的话就要把所有策略暴露出去。一个简单工厂即可搞定。

SyncStrategyFactory

/*** 同步策略工厂类接口**/
public interface SyncStrategyFactory {Map<Integer, String> STRATEGY_MAP = Map.of(1, SyncStrategy.OT_STRATEGY,2, SyncStrategy.AB_STRATEGY,3, SyncStrategy.DEFAULT_STRATEGY);/*** 根据任务类型获取具体策略** @param taskType 任务类型* @return 具体策略*/SyncStrategy getStrategy(Integer taskType);/*** 执行策略  // XXX: 其实这块放这里有背单一职责的,同时也不符合Factory本意。** @param ctx 策略上下文*/void exec(SyncContext ctx);
}
复制代码

SyncStrategyFactoryImpl

/*** 策略工厂具体实现**/
@Slf4j
@Service
@RequiredArgsConstructor
public class SyncStrategyFactoryImpl implements SyncStrategyFactory {// 这块可以按Spring Bean别名注入private final Map<String, SyncStrategy> strategyMap;@Overridepublic SyncStrategy getStrategy(Integer taskType) {if (!STRATEGY_MAP.containsKey(taskType) || !strategyMap.containsKey(STRATEGY_MAP.get(taskType))) {return null;}return strategyMap.get(STRATEGY_MAP.get(taskType));}@Overridepublic void exec(SyncContext ctx) {Optional.of(getStrategy(ctx.getTaskType())).ifPresent(strategy -> {log.info("[策略执行] 查找策略 {}, ctx=>{}", strategy.getClass().getSimpleName(), ctx);strategy.process(ctx);log.info("[策略执行] 执行完成 ctx=>{}", ctx);});}
}
复制代码

至此,可以很方便的在Spring环境中,通过注入SyncStrategyFactory来调用。

最后补上单测

/*** 策略单测**/
@Slf4j
@SpringBootTest
class SyncStrategyFactoryTest {@AutowiredSyncStrategyFactory strategyFactory;@Testvoid testOtStrategy() {final SyncContext ctx = SyncContext.builder().taskType(1).build();strategyFactory.exec(ctx);Assertions.assertEquals("otStrategy", ctx.getRespVO());}@Testvoid testAbStrategy() {final SyncContext ctx = SyncContext.builder().taskType(2).build();strategyFactory.exec(ctx);Assertions.assertEquals("abStrategy", ctx.getRespVO());}@Testvoid testDefaultStrategy() {final SyncContext ctx = SyncContext.builder().taskType(3).build();strategyFactory.exec(ctx);Assertions.assertEquals("defaultStrategy", ctx.getRespVO());}@Testvoid testOtherStrategy() {final SyncContext ctx = SyncContext.builder().taskType(-1).build();strategyFactory.exec(ctx);Assertions.assertNull(ctx.getRespVO());}
}
复制代码

1.2 动态(参数)策略

其实在上面的策略模式中,也可以将taskType放到具体策略中,作为一个元数据处理。在选择具体策略时,遍历所有策略实现类,当taskType与当前参数匹配时则终止遍历,由当前策略类处理。

在上述落地页注册中,向CRM同步数据时,需要校验的数据比较多。因为不同地区落地页参数各不相同,同时有些历史落地页。

这种其实可以在策略类中添加校验方法,如boolean match(StrategyContext ctx)。具体见代码

LayoutContext

/*** 布局上下文**/
@Data
@Builder
public class LayoutContext {// 落地页版本(Landing Page Version)private String lpv;// 国家地区private String country;// 渠道号private String channel;// 最终处理结果 拿到布局IDprivate String layoutId;
}
复制代码

LayoutStrategy

/*** 布局处理策略**/
public interface LayoutStrategy {/*** 校验是否匹配该策略** @param ctx 策略上下文* @return bool*/boolean match(LayoutContext ctx);/*** 具体策略处理** @param ctx 策略上下文*/void process(LayoutContext ctx);
}
复制代码

具体布局处理策略

/*** 幼儿布局**/
@Slf4j
@Order(10)
@Service
public class LayoutChildStrategy implements LayoutStrategy {// 幼儿特殊渠道号(优先级最高)private static final String CHILD_CHANNEL = "FE-XX-XX-XX";@Overridepublic boolean match(LayoutContext ctx) {return Objects.nonNull(ctx) && CHILD_CHANNEL.equals(ctx.getChannel());}@Overridepublic void process(LayoutContext ctx) {log.info("[幼儿布局] 开始处理");ctx.setLayoutId("111");}
}
复制代码
/*** 根据LPV进行判断的策略*/
@Slf4j
@Order(20)
@Service
public class LayoutLpvStrategy implements LayoutStrategy {// 需要走LPV处理逻辑的渠道号private static final Set<String> LPV_CHANNELS = Set.of("LP-XX-XX-01", "LP-XX-XX-02", "XZ-XX-XX-01", "XZ-XX-XX-02");@Overridepublic boolean match(LayoutContext ctx) {return Objects.nonNull(ctx) && Objects.nonNull(ctx.getChannel()) && LPV_CHANNELS.contains(ctx.getChannel());}@Overridepublic void process(LayoutContext ctx) {log.info("[LPV布局] 开始处理");ctx.setLayoutId("222");}
}
复制代码
/*** 默认处理策略*/
@Slf4j
@Order(999)
@Service
public class LayoutDefaultStrategy implements LayoutStrategy {@Overridepublic boolean match(LayoutContext ctx) {// 兜底策略return true;}@Overridepublic void process(LayoutContext ctx) {log.info("[默认布局] 开始处理");ctx.setLayoutId("999");}
}
复制代码

最后,工厂类:

/*** 布局处理工厂**/
public interface LayoutProcessFactory {/*** 获取具体策略** @param ctx 上下文* @return Strategy*/Optional<LayoutStrategy> getStrategy(LayoutContext ctx);/*** 策略调用** @param ctx 上下文*/void exec(LayoutContext ctx);
}
复制代码
/*** 布局处理工厂实现*/
@Slf4j
@Service
@RequiredArgsConstructor
public class LayoutProcessFactoryImpl implements LayoutProcessFactory {// Spring会根据@Order注解顺序注入private final List<LayoutStrategy> strategyList;@Overridepublic Optional<LayoutStrategy> getStrategy(LayoutContext ctx) {return strategyList.stream().filter(s -> s.match(ctx)).findFirst();}@Overridepublic void exec(LayoutContext ctx) {log.info("[布局处理] 尝试处理 ctx=>{}", ctx);getStrategy(ctx).ifPresent(s -> {s.process(ctx);log.info("[布局处理] 处理完成 ctx=>{}", ctx);});}
}
复制代码

最后的最后,单测:

@SpringBootTest
class LayoutProcessFactoryTest {@Autowiredprivate LayoutProcessFactory processFactory;@Testvoid testChild() throws IllegalAccessException {// 通过反射获取Channelfinal Field childChannel = ReflectionUtils.findField(LayoutChildStrategy.class, "CHILD_CHANNEL");assertNotNull(childChannel);childChannel.setAccessible(true);  // XXX: setAccessible 后续可能会禁止这样使用String childChannelStr = (String) childChannel.get(LayoutChildStrategy.class);// 初始化ContextLayoutContext ctx = LayoutContext.builder().channel(childChannelStr).build();//processFactory.exec(ctx);assertEquals("111", ctx.getLayoutId());}@Testvoid testLpv() {LayoutContext ctx = LayoutContext.builder().channel("LP-XX-XX-02").build();processFactory.exec(ctx);assertEquals("222", ctx.getLayoutId());}@Testvoid testDefault() {final LayoutContext ctx = LayoutContext.builder().build();processFactory.exec(ctx);assertEquals("999", ctx.getLayoutId());}
}
复制代码

2 思考

策略模式能给我们带来什么?

  1. 对业务逻辑进行了一定程度的封装,将不易变和易变逻辑进行了分离。使得后续的业务变更,仅修改相应的策略或者新增策略即可。
  2. 但再深层思考一下。之前易变和不易变逻辑修改代价可能相差不大,而使用设计模式之后,使得易变代码修改代价降低,但不易变代码修改代价则上升。所以在使用时要三思而后行。
  3. 策略模式消除了if-else吗?好像没有,只是把这个选择权向后移(或者说交给调用者)了。
  4. 策略让原本混杂在一个文件甚至是一个函数里面的代码,打散到数个文件中。如果每块逻辑只是简单的几行代码,使用策略反而会得不偿失。还不如if-else或者switch浅显易懂、一目了然。

策略模式跟其他模式有啥区别?

  1. 模板模式有点像。不过模板模式主要是在父类(上层)对一些动作、方法做编排。而由不同子类去做具体动作、方法的实现。重点在于编排。
  2. 桥接模式有点像。不过桥接有多个维度的变化,策略可以认为是一维的桥接。

3 后续

本打算一篇文章将常用的设计模式一块讲讲,贴上代码似乎有点长,还是分开说吧。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_37203.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(C语言)printf打印的字符串太长了,我想分两行!

本文来自于公众号&#xff1a;C语言编程技术分享 一、提问 有下述C程序&#xff1a; #include <stdio.h> #include <stdlib.h>int main() { printf("123456789012345678901234567890\n");system("pause");return 0; } printf函数要打印的字…

B. Elimination of a Ring Pinely Round 1 (Div. 1 + Div. 2)

传送门 题目意思&#xff1a; 给你一个为环的序列n&#xff0c;这个序列有一个特殊的地方&#xff1a;就是如果有相邻元素相等他就会立马删除其中一个元素&#xff08;第一个和最后一个相邻&#xff09;。 然后你每次可以删除一个元素&#xff0c;问你能删除的最多的操作数是多…

想做副业没有方向,这三条告诉你什么是副业思维

怎样副业赚钱&#xff1f;教你几招&#xff0c;掌控自己的固有思维 你了解钱是怎么来的吗&#xff1f;你如果弄不懂这一点&#xff0c;你也是很难赚到钱的。 钱不是苦的&#xff0c;辛苦努力挣的基本都是一点钱。 假如将你的工作时长换为钱&#xff0c;你可以清晰地赚多少钱…

【情感识别】BP神经网络语音情感识别【含Matlab源码 349期】

⛄一、BP神经网络语音情感识别简介 0 引言 随着科技的迅速发展, 人机交互显得尤为重要。语音是语言的载体, 是人与人之间交流的重要媒介。相较于其它交流方式而言, 语音交流更加直接、便捷。近年来, 随着人机交互研究的不断深入, 语音情感识别更成为了学术界研究的热点, 其涉及…

计算机键盘用途及快捷键

用途&#xff1a; 电脑键盘上有那么多按键&#xff0c;到底都有什么作用呢&#xff1f; 几个重要的按键&#xff0c;一起来了解一下吧。 最上面一排&#xff1a; F1帮助 F2改名 F3搜索 F4地址 F5刷新 F6切换 F10菜单 1、键盘中间区域的所有输入按键。 一共是26个英文字母…

【微服务】springboot + dubbo 整合Sentinel限流

一、前言 限流对一个生产环境的系统来说&#xff0c;具有重要的意义&#xff0c;限流的目的是为了保护系统中的某些核心业务资源不被瞬间的大并发流量冲垮而采取的一种措施&#xff0c;因此一个成熟的架构设计方案&#xff0c;限流也需要纳入到架构设计和规划中。 二、常用的限…

【Linux系统】第二篇、权限管理篇

文章目录一、Linux下的用户二、文件的权限1. 文件访问者的分类2. 文件类型和访问权限3. 文件权限值的表示方法三、文件访问权限的相关设置方法1. chmod2. chown3. chgrp4. umask&#xff08;重点&#xff09;四、file指令五、目录的权限粘滞位一、Linux下的用户 这里我们在上一…

【雷达通信】雷达探测项目仿真附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

多线程(1)

多线程 前言 &#xff1a; 上文主要了解到了进程&#xff0c; 那么为啥需要引入进程呢&#xff1f;   或者说为啥要有进程呢&#xff1f; 其实这里最主要的目的是为了解决 并发编程 这样的问题。 了解 &#xff1a;   这里 cpu 进入了多核心的时代&#xff0c;想要进一步提…

3天3定制大屏,反向PUA

摘要 本次分享一段无讨价还价余地的单人3天定制化大屏全过程&#xff08;强调说拖拽屏的请绕道,和你想的不一样&#xff09;,要动效、要地图、要流光。天坑的心理博弈到最终解决的过程及技术思路。 前因 没啥征兆突然接到说&#xff0c;要在下周完成2个大屏的定制开发,起初没提…

身份安全风险分析

摘要 从勒索软件到 APT&#xff0c;身份风险是重要的攻击向量。 管理 Active Directory 的复杂性&#xff0c;导致所有组织都存在1/6的可利用的特权身份风险。 这些身份风险包括使用过时密码的本地管理员、具有不必要权限的错误配置用户、在终端上暴露的缓存凭据等。 当攻击者…

arthas进阶版排查问题之idea插件工具操作

arthas前面的文章讲了怎么去使用命令排查线上问题&#xff0c;线上出了问题就需要我们去排查问题和处理程序异常&#xff0c;但是线上一般出问题不太好解决&#xff0c;总有一些奇怪的问题&#xff0c;当然很多场景是测试测试不到的&#xff0c;我们不能百分百保证线上不出问题…

阿里大咖纯手写的微服务入门笔记,从基础到进阶直接封神

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生的…

AcWing 搜素与图论

搜索 DFS 全排列 代码 #include<iostream> using namespace std;int vis[10], a[10];void dfs(int step, int n) {if (step n 1){for (int i 1; i < n; i)printf("%d ", a[i]);printf("\n");return;}for (int i 1; i < n; i){if (!vis[i…

【调优】大数据常见 Join 的使用场景

【调优】大数据常见 Join 的使用场景 上次写了大表和大表 join 的调优方法&#xff0c;今天总结一下大数据常见的 Join 方法。 1.Shuffle Join 大数据采用的是分布式存储&#xff0c;一个表的数据会分散在各个节点。为了进行 join&#xff0c;通常都会进行 shuffle 操作&…

ESG,TO B长期主义里的「新战役」

中国企业最好的方式是从初始阶段就植入ESG基因&#xff0c;使它逐渐从隐形变成显性基因。长期坚持此类发展导向&#xff0c;对后续发展ESG战略&#xff0c;提升ESG合规能力也将成为一种积累和准备。 作者|三七 编辑|皮爷 出品|产业家 20世纪初期&#xff0c;伦敦得到一个延…

HttpMessageConverter 消息转换器

HttpMessageConverter 简介 HttpMessageConverter 是SpringMVC中提供的一个策略接口&#xff0c;它是一个消息转换器类&#xff0c;Spring Mvc中就是由HttpMessageConverter负责转换HTTP的请求和响应。 默认情况下&#xff0c;Spring Boot 会自动加载如下消息类型转换器&…

数字验证学习笔记——UVM学习1

一、类库地图 在SV模块中&#xff0c;验证环境整体的构建&#xff0c;是从底层模块的验证组件搭建到通信和激励生成这些元素无论是软件对象的创建、访问、修改、配置&#xff0c;还是组件之间的通信等都是通过用户自定义的方式来实现的。UVM验证方法学作为之前所有方法学的融合…

【语音去噪】谱减法+维纳滤波+卡尔曼滤波语音去噪【含Matlab源码 1881期】

⛄一、谱减法维纳滤波卡尔曼滤波语音去噪简介 1 维纳滤波算法 在传统的去噪算法中,维纳滤波因其操作简单、去噪效果好,被公认为一种经典的去噪算法。语音信号在时域的表示为: yi( t) si( t) ni( t) ,其中si( t) 、ni( t) 和yi( t) 分别是第i帧原始语音信号、噪声和被噪声污染…

java检验mp4文件完整性的一个方法:使用ffmpeg

问题引入 最近笔者在写一个多线程下载视频文件的程序&#xff0c;打算让这个程序在我的空闲服务器上运行&#xff0c;但是几轮测试之后发现&#xff0c;有时候会存在下载的视频文件不完整的情况&#xff0c;这就导致了有些文件无法正常播放 问题排查 经过一周的排查后&#…