Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >工作中常用的设计模式--策略模式

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

作者头像
lpe234
发布于 2022-11-28 07:43:57
发布于 2022-11-28 07:43:57
28800
代码可运行
举报
文章被收录于专栏:若是烟花若是烟花
运行总次数:0
代码可运行

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

1 策略模式(Strategy Pattern)

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

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

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

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

1.1 静态(参数)策略

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

SyncContext

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 同步上下文
 *
 */
@Data
@Builder
public class SyncContext {
    // 任务ID
    private Long taskId;
    // 任务类型 1: 自然注册; 2: 团购用户; 3: 落地页留资
    private Integer taskType;
    // 所有留资相关信息(忽略细节)
    private Object reqVO;

	// 存储执行策略名称(伪装执行结果)
    private String respVO;
}

SyncStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 同步策略
 *
 */
public interface SyncStrategy {

    /**
     * 具体策略
     * @param ctx Context
     */
    void process(SyncContext ctx);
}

OtSyncStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 自然注册
 *
 */
@Slf4j
@Service
public class OtSyncStrategy implements SyncStrategy, BeanNameAware {
    private String beanName;

    @Override
    public void process(SyncContext ctx) {
        log.info("[自然注册] {}", ctx);
        ctx.setRespVO(beanName);
    }

    @Override
    public void setBeanName(String s) {
        beanName = s;
    }
}

AbSyncStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 团购用户
 *
 */
@Slf4j
@Service
public class AbSyncStrategy implements SyncStrategy, BeanNameAware {
    private String beanName;

    @Override
    public void process(SyncContext ctx) {
        log.info("[团购用户] {}", ctx);
        ctx.setRespVO(beanName);
    }

    @Override
    public void setBeanName(String s) {
        beanName = s;
    }
}

DefaultSyncStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 落地页注册(Default)
 *
 */
@Slf4j
@Service
public class DefaultSyncStrategy implements SyncStrategy, BeanNameAware {
    private String beanName;

    @Override
    public void process(SyncContext ctx) {
        log.info("[落地页注册] {}", ctx);
        ctx.setRespVO(beanName);
    }

    @Override
    public void setBeanName(String s) {
        beanName = s;
    }
}

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

SyncStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 同步策略
 *
 */
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 自然注册
 *
 */
@Slf4j
@Service(SyncStrategy.OT_STRATEGY)
public class OtSyncStrategy implements SyncStrategy, BeanNameAware {
    private String beanName;

    @Override
    public void process(SyncContext ctx) {
        log.info("[自然注册] {}", ctx);
        ctx.setRespVO(beanName);
    }

    @Override
    public void setBeanName(String s) {
        beanName = s;
    }
}

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

SyncStrategyFactory

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 同步策略工厂类接口
 *
 */
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 策略工厂具体实现
 *
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SyncStrategyFactoryImpl implements SyncStrategyFactory {

    // 这块可以按Spring Bean别名注入
    private final Map<String, SyncStrategy> strategyMap;

    @Override
    public SyncStrategy getStrategy(Integer taskType) {
        if (!STRATEGY_MAP.containsKey(taskType) || !strategyMap.containsKey(STRATEGY_MAP.get(taskType))) {
            return null;
        }
        return strategyMap.get(STRATEGY_MAP.get(taskType));
    }

    @Override
    public 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来调用。

最后补上单测

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 策略单测
 *
 */
@Slf4j
@SpringBootTest
class SyncStrategyFactoryTest {

    @Autowired
    SyncStrategyFactory strategyFactory;

    @Test
    void testOtStrategy() {
        final SyncContext ctx = SyncContext.builder().taskType(1).build();
        strategyFactory.exec(ctx);
        Assertions.assertEquals("otStrategy", ctx.getRespVO());
    }

    @Test
    void testAbStrategy() {
        final SyncContext ctx = SyncContext.builder().taskType(2).build();
        strategyFactory.exec(ctx);
        Assertions.assertEquals("abStrategy", ctx.getRespVO());
    }

    @Test
    void testDefaultStrategy() {
        final SyncContext ctx = SyncContext.builder().taskType(3).build();
        strategyFactory.exec(ctx);
        Assertions.assertEquals("defaultStrategy", ctx.getRespVO());
    }

    @Test
    void 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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 布局上下文
 *
 */
@Data
@Builder
public class LayoutContext {
    // 落地页版本(Landing Page Version)
    private String lpv;

    // 国家地区
    private String country;
    // 渠道号
    private String channel;

    // 最终处理结果 拿到布局ID
    private String layoutId;
}

LayoutStrategy

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 布局处理策略
 *
 */
public interface LayoutStrategy {

    /**
     * 校验是否匹配该策略
     *
     * @param ctx 策略上下文
     * @return bool
     */
    boolean match(LayoutContext ctx);

    /**
     * 具体策略处理
     *
     * @param ctx 策略上下文
     */
    void process(LayoutContext ctx);
}

具体布局处理策略

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 幼儿布局
 *
 */
@Slf4j
@Order(10)
@Service
public class LayoutChildStrategy implements LayoutStrategy {
    // 幼儿特殊渠道号(优先级最高)
    private static final String CHILD_CHANNEL = "FE-XX-XX-XX";

    @Override
    public boolean match(LayoutContext ctx) {
        return Objects.nonNull(ctx) && CHILD_CHANNEL.equals(ctx.getChannel());
    }

    @Override
    public void process(LayoutContext ctx) {
        log.info("[幼儿布局] 开始处理");
        ctx.setLayoutId("111");
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 根据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"
    );

    @Override
    public boolean match(LayoutContext ctx) {
        return Objects.nonNull(ctx) && Objects.nonNull(ctx.getChannel()) && LPV_CHANNELS.contains(ctx.getChannel());
    }

    @Override
    public void process(LayoutContext ctx) {
        log.info("[LPV布局] 开始处理");
        ctx.setLayoutId("222");
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 默认处理策略
 */
@Slf4j
@Order(999)
@Service
public class LayoutDefaultStrategy implements LayoutStrategy {

    @Override
    public boolean match(LayoutContext ctx) {
        // 兜底策略
        return true;
    }

    @Override
    public void process(LayoutContext ctx) {
        log.info("[默认布局] 开始处理");
        ctx.setLayoutId("999");
    }
}

最后,工厂类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 布局处理工厂
 *
 */
public interface LayoutProcessFactory {

    /**
     * 获取具体策略
     *
     * @param ctx 上下文
     * @return Strategy
     */
    Optional<LayoutStrategy> getStrategy(LayoutContext ctx);

    /**
     * 策略调用
     *
     * @param ctx 上下文
     */
    void exec(LayoutContext ctx);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 布局处理工厂实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LayoutProcessFactoryImpl implements LayoutProcessFactory {

    // Spring会根据@Order注解顺序注入
    private final List<LayoutStrategy> strategyList;

    @Override
    public Optional<LayoutStrategy> getStrategy(LayoutContext ctx) {
        return strategyList.stream()
                .filter(s -> s.match(ctx)).findFirst();
    }

    @Override
    public void exec(LayoutContext ctx) {
        log.info("[布局处理] 尝试处理 ctx=>{}", ctx);
        getStrategy(ctx).ifPresent(s -> {
            s.process(ctx);
            log.info("[布局处理] 处理完成 ctx=>{}", ctx);
        });
    }
}

最后的最后,单测:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@SpringBootTest
class LayoutProcessFactoryTest {

    @Autowired
    private LayoutProcessFactory processFactory;

    @Test
    void testChild() throws IllegalAccessException {
        // 通过反射获取Channel
        final Field childChannel = ReflectionUtils.findField(LayoutChildStrategy.class, "CHILD_CHANNEL");
        assertNotNull(childChannel);
        childChannel.setAccessible(true);  // XXX: setAccessible 后续可能会禁止这样使用
        String childChannelStr = (String) childChannel.get(LayoutChildStrategy.class);
        // 初始化Context
        LayoutContext ctx = LayoutContext.builder().channel(childChannelStr).build();
        //
        processFactory.exec(ctx);
        assertEquals("111", ctx.getLayoutId());
    }

    @Test
    void testLpv() {
        LayoutContext ctx = LayoutContext.builder().channel("LP-XX-XX-02").build();
        processFactory.exec(ctx);
        assertEquals("222", ctx.getLayoutId());
    }

    @Test
    void 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 后续

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


封面图来源: https://refactoring.guru/design-patterns/strategy

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
设计模式之策略模式
订阅视频平台事件(异常振动,蒸汽泄露,未带安全帽…),每个类型的事件对应一个处理类(策略类),后续可能还会订阅其他的事件.
九转成圣
2024/04/10
960
设计模式之策略模式
Java策略模式那些事儿(一)
策略模式其实也是在解耦,把策略的定义、创建、使用这三个部分解耦开来,因为本身策略模式也是基于接口编程,这样其实可以简单的理解客户端调用使用接口进行编程,可以通过工厂方法创建对应的策略模式,进而完成对应的程序功能。这么说起来有点拗口,我们直接看程序吧。
猫头虎
2024/04/07
1370
设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。
黄规速
2022/04/14
4200
设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
如果策略模式的代码有段位,你的是白银?黄金?还是王者?
在软件开发中,我们经常会遇到一些场景,其中业务流程大致相同,但具体的操作步骤或算法却可能因为某些条件的不同而有所变化。为了应对这种情况,设计模式中的“策略模式”提供了一种优雅的解决方案。 本文将探讨策略模式的概念、应用场景、以及不同的实现方式,希望这个分享能节省大家的开发时间,这样可以有更多的时间来做更多想做的事,譬如陪陪家人。
烟雨平生
2024/04/23
1020
如果策略模式的代码有段位,你的是白银?黄金?还是王者?
SpringBoot下的策略模式,消灭了大量的ifelse,真香!
项目中有这样一个场景,在公园放置了用来拍摄人像的识别杆,根据用户在不同识别杆之间采集的图象来计算用户的运动距离。由于涉及到许多公园,每个公园的布局不同,识别杆之间距离不同,算法也不同。但代码中每个不同的公园的算法区别都采用ifelse来进行判断处理。
程序新视界
2020/10/29
3K0
SpringBoot下的策略模式,消灭了大量的ifelse,真香!
java设计模式之策略模式
策略模式 策略模式的定义是:定义了一系列的算法,把它们一个个的封装起来,并且使它们可相互替换,让算法可以独立于使用它的客户而变化。 设计原则是:把一个类中经常改变或者将来可能会经常改变的部分提取出来作为一个接口,然后在使用类中包含这个接口的实例,这样使用类的对象就可以随意调用实现了这个接口的类行为。 在策略模式中有如下几个角色: 环境角色(Context): 此角色中实现了对策略角色的引用和使用。 抽象策略角色:此角色通常由抽象类或接口来实现,定义了,所以具体策略角色的行为。 具体策略角色:此角色封装了实现
纪莫
2018/06/14
4570
Java设计模式之(十四)——策略模式
策略模式(Strategy Pattern):定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
IT可乐
2021/12/04
4010
Java设计模式之(十四)——策略模式
工作中常用的设计模式--适配器模式
已存在的接口、服务,跟我们所需、目的接口不兼容时,我们需要通过一定的方法将二者进行兼容适配。一个常见的例子,家用电源(国标)220V,而手机标准输入一般为5V,此时我们便需要一个适配器来将220V转换为5V使用。
lpe234
2022/11/30
2270
基于【策略模式】设计多渠道发送消息
前言:设计模式源于生活 策略模式的基本概念 策略模式将可变的部分从程序中抽象分离成算法接口,在该接口下分别封装一系列算法实现,并使他们可以相互替换,从而导致客户端程序独立于算法的改变。 策略模式应用场景 1.解决我多重if条件判断 2.有共同行为,但是有不同的业务逻辑(例如:支付模式[支持多种支付模式],直播线路模式[支持多种线路切换],消息发送渠道模式[支持多种消息渠道发送]) 策略模式优缺点 优点: 1.扩展性良好 2.算法自由切换 3.更好的代码复用性 缺点: 1
黎明大大
2020/09/08
1.1K0
基于【策略模式】设计多渠道发送消息
工作中常见的设计模式-策略模式
最近准备学习下之前项目中用到的设计模式,这里代码都只展示核心业务代码,省略去大多不重要的代码。
一枝花算不算浪漫
2019/09/05
8290
工作中常见的设计模式-策略模式
策略模式+工厂服务实现规则过滤
策略模式(Strategy Pattern)是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户,从而达到算法的变化不会影响到客户。这种模式涉及到三个角色:
用户11097514
2024/05/31
2500
策略模式+工厂服务实现规则过滤
策略模式在业务中的实际应用
策略模式主要由以上三个身份组成,这里我们就不过多介绍策略模式的基础知识,默认大家已经对策略模式已经有了一个基础的认识。
一个程序员的成长
2021/11/10
3640
策略模式在业务中的实际应用
小谈网关项目中的设计模式
在网关项目中,单例模式是出现频率最高的模式。同时,所有的单例对象被 IoC 框架 Guice 统一管理。
用户1161731
2020/02/18
8890
还在用if else?策略模式了解一下!
小编在公司负责的就是订单取消业务,老系统中各种类型订单取消都是通过if else 判断不同的订单类型进行不同的逻辑。在经历老系统的折磨和产品需求的不断变更,小编决定进行一次大的重构:消灭 if else。
cxuan
2019/08/16
8680
还在用if else?策略模式了解一下!
策略模式
最近有一个学妹在跟我沟通如何有效的去避免代码中一长串的if else判断或者switch条件判断?针对更多的回答就是合理的去使用设计来规避这个问题。
敖丙
2021/07/02
4880
一文搞懂设计模式—策略模式
在软件开发中,经常会遇到需要根据不同的条件来实现不同行为的场景。这种场景下,策略模式(Strategy Pattern)就是一种非常有用的设计模式。
BookSea
2024/01/30
3530
一文搞懂设计模式—策略模式
工厂模式和策略模式结合使用的案例介绍
在前面的文章中,我们有单独介绍过工厂模式和策略模式,这两种模式是实际开发中经常会用到的,今天来介绍下将两种模式结合起来使用的场景及案例,这种结合的模式也更加的常用,能帮助我们减少if-else的使用的同时,让代码逻辑也清晰简洁、扩展性高。
用户4283147
2022/10/08
1.2K0
CRUD很无聊?一起学设计模式吧!--策略模式
策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
JAVA日知录
2019/10/19
4560
CRUD很无聊?一起学设计模式吧!--策略模式
教你如何使用策略模式
在策略模式中一个类的行为或者其算法在运行是可以进行改变,这种的类型也可以叫做行为型模式。
青衫染红尘
2021/01/15
1K0
教你如何使用策略模式
【玩烂设计模式】设计模式之策略模式
什么是策略模式 策略模式就是定义了算法家族 分别封装起来 让它们之间互相替换 从模式让算法的变化 不会影响到使用算法的客户。
周杰伦本人
2022/10/25
3740
【玩烂设计模式】设计模式之策略模式
相关推荐
设计模式之策略模式
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验