Spring 环境下实现策略模式的示例

背景

最近在忙一个需求,大致就是给满足特定条件的用户发营销邮件,但是用户的来源有很多方式:从 ES 查询的、从 csv 导入的、从 MongoDB 查询….. 需求很简单,但是怎么写的优雅,方便后续扩展,就存在很多门道了。

我们的项目是基于 Spring Boot 开发的,因此这篇文章也会基于 Spring Boot 作为基础框架,教你如何使用 Spring 依赖注入的特性,优雅的实现策略模式。

1. 简单粗暴

最简单粗暴直接的方式莫过于 if...else… 了,伪代码如下:

if(来源 == ES){
 // TODO: ES Query
}else if(来源 == CSV){
 // TODO: Read CSV File
}else if(来源 == MongoDB){
 // TODO: MongoDB Query
}

如果后面还需要从其他平台获取,那就在接着添加 else if...,这种方式固然简单直接,但是当后续扩展的方式越来越多,相应的if...else...也会越来越长,emmm….. 怎么说呢,黑猫白猫,能抓到老鼠的就是好猫。

2. 策略模式

在 Spring 环境下实现策略模式异常简单,毕竟 Spring 提供的依赖注入简直就是开发利器~

既然是策略模式,那么定义策略肯定是首当其冲,策略我们使用枚举实现最佳。

public enum GroupType {
  /**
   * 从 ES 查询
   */
  ES,
  /**
   * 从 MongoDB 查询
   */
  MONGODB,
  /**
   * 从 文件 读取
   */
  FILE
}

下一步,我们定义一个接口,用于抽象通用的功能。

public interface IGroupSelect {
  /**
   * 人群获取方式
   *
   * @return 人群选择枚举
   */
  GroupType type();

  /**
   * 查询满足条件的用户
   *
   * @param groupQuery 查询条件
   * @return 满足条件的用户
   */
  default List<GroupUser> queryUser(GroupQuery groupQuery) {
    checkQueryCondition(groupQuery);
    return doQuery(groupQuery);
  }

  /**
   * 事前校验查询条件
   *
   * @param groupQuery 查询条件
   * @throws IllegalArgumentException 参数异常
   */
  void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException;

  /**
   * 真正的查询方法
   *
   * @param groupQuery 查询条件
   * @return 满足条件的用户
   */
  List<GroupUser> doQuery(GroupQuery groupQuery);

}

这一步,小伙伴们有没有发现里面也包含了模板方法模式呢?

然后就是不同策略的具体实现了。

  • ES 策略
@Slf4j
@Service
public class EsGroupSelect implements IGroupSelect {

  /**
   * 人群获取方式
   *
   * @return 人群选择枚举
   */
  @Override
  public GroupType type() {
    return GroupType.ES;
  }

  /**
   * 事前校验查询条件
   *
   * @param groupQuery 查询条件
   * @throws IllegalArgumentException 参数异常
   */
  @Override
  public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException {
    log.info("groupQuery = {}", groupQuery);
  }

  /**
   * 查询满足条件的用户
   *
   * @param groupQuery 查询条件
   * @return 满足条件的用户
   */
  @Override
  public List<GroupUser> doQuery(GroupQuery groupQuery) {
    List<GroupUser> result = new ArrayList<>();
    // TODO:
    // 1. 复杂的 ES 查询逻辑
    // 2. 根据条件筛选满足条件的用户数据
    for (int i = 1; i <= 15; i++) {
      result.add(GroupUser.of("ES用户" + i, i + "@es.com"));
    }
    return result;
  }
}
  • 文件策略
@Slf4j
@Service
public class FileGroupSelect implements IGroupSelect {
  /**
   * 人群获取方式
   *
   * @return 人群选择枚举
   */
  @Override
  public GroupType type() {
    return GroupType.FILE;
  }

  /**
   * 事前校验查询条件
   *
   * @param groupQuery 查询条件
   * @throws IllegalArgumentException 参数异常
   */
  @Override
  public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException {
    log.info("groupQuery = {}", groupQuery);
  }

  /**
   * 查询满足条件的用户
   *
   * @param groupQuery 查询条件
   * @return 满足条件的用户
   */
  @Override
  public List<GroupUser> doQuery(GroupQuery groupQuery) {
    List<GroupUser> result = new ArrayList<>();
    // TODO:
    // 1. 复杂的解析、读文件
    // 2. 根据条件筛选满足条件的用户数据
    for (int i = 1; i <= 3; i++) {
      result.add(GroupUser.of("文件读取用户" + i, i + "@file.com"));
    }
    return result;
  }
}
  • MongoDB 策略
@Slf4j
@Service
public class MongoGroupSelect implements IGroupSelect {
  /**
   * 人群获取方式
   *
   * @return 人群选择枚举
   */
  @Override
  public GroupType type() {
    return GroupType.MONGODB;
  }

  /**
   * 事前校验查询条件
   *
   * @param groupQuery 查询条件
   * @throws IllegalArgumentException 参数异常
   */
  @Override
  public void checkQueryCondition(GroupQuery groupQuery) throws IllegalArgumentException {
    log.info("groupQuery = {}", groupQuery);
  }

  /**
   * 查询满足条件的用户
   *
   * @param groupQuery 查询条件
   * @return 满足条件的用户
   */
  @Override
  public List<GroupUser> doQuery(GroupQuery groupQuery) {
    List<GroupUser> result = new ArrayList<>();
    // TODO:
    // 1. 复杂的 MongoDB 查询逻辑
    // 2. 根据条件筛选满足条件的用户数据
    for (int i = 1; i <= 7; i++) {
      result.add(GroupUser.of("MongoDB用户" + i, i + "@mongo.com"));
    }
    return result;
  }
}

现在到了最后一步,就是如何通过 Spring 优雅的实现策略模式的选择呢?敲黑板,考试必考!

我们通过定义一个工厂类,然后使用 Spring 的依赖注入特性,可以注入一个接口的多个实现,这里采用 List<IGroupSelect> 的形式注入,Spring 也支持通过 Map<String,IGroupSelect> 的形式注入,如果使用 Map 注入,那么 key 就是类名,小伙伴们自己也可以测试一下~

@Service
public class GroupSelectFactory {
  @Autowired
  private List<IGroupSelect> groupSelectList;

  /**
   * 根据人群类型选择具体的实现类
   *
   * @param type 人群类型
   * @return 人群选择具体实现类
   */
  public IGroupSelect getGroupSelect(GroupType type) {
    Optional<IGroupSelect> groupSelectOptional = groupSelectList.stream().filter(t -> t.type() == type).findAny();
    return groupSelectOptional.orElseThrow(() -> new IllegalArgumentException("暂不支持该人群方式"));
  }
}

最后写个定时任务测试一下吧。

@Autowired
private GroupSelectFactory groupSelectFactory;

/**
 * 模拟定时发送营销邮件
 */
@Scheduled(cron = "0/10 * * * * ?")
public void sendEmailTask() {
  List<SendEmailTask> taskList = new ArrayList<>();
  for (GroupType groupType : GroupType.values()) {
    GroupQuery groupQuery = new GroupQuery("虚头巴脑的 " + groupType.name() + " 查询条件");
    taskList.add(SendEmailTask.of(groupType, groupQuery));
  }

  taskList.forEach(task -> {
    List<GroupUser> groupUsers = groupSelectFactory.getGroupSelect(task.getType()).queryUser(task.getQuery());
    log.info("groupUsers = {}", groupUsers);
  });

}

@Data
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
static class SendEmailTask implements Serializable {
  private static final long serialVersionUID = -3461263089669779193L;
  private GroupType type;
  private GroupQuery query;
}

观察控制台,看看日志输出吧~

总结

  • 本文使用策略模式实现不同人群的查询,后续如果要增加短信、微信、钉钉的消息发送,是不是也可以用策略模式实现呢?
  • 使用 Spring 的依赖注入特性,可以注入一个接口的多个实现,很容易就实现了策略模式的选择,这样后续添加一种策略的时候,完全不需要改动主要逻辑,只需添加具体实现即可。
  • 细心的小伙伴可以发现,本文虽然是讲策略模式,其实里面还包含了模板方法、工厂模式,多种设计模式的协同作战,食用味道更佳哟~

配套代码:https://github.com/xkcoding/practice_demo/tree/master/strategy-design-pattern-in-spring

以上就是Spring 环境下实现策略模式的示例的详细内容,更多关于Spring 实现策略模式的资料请关注我们其它相关文章!

(0)

相关推荐

  • php策略模式简单示例分析【区别于工厂模式】

    本文实例讲述了php策略模式.分享给大家供大家参考,具体如下: 策略模式和工厂模式很像. 工厂模式:着眼于得到对象,并操作对象. 策略模式:着重得到对象某方法的运行结果. 示例: //实现一个简单的计算器 interface MathOp{ public function calculation($num1,$num2); } //加法 class MathAdd implements MathOp{ public function calculation($num1,$num2){ retur

  • php 策略模式原理与应用深入理解

    本文实例讲述了php 策略模式原理与应用.分享给大家供大家参考,具体如下: 策略模式 简单理解就是 有n个做法供你选择,根据你的需要选择某个策略得到结果 就应用场景来说: 例1:比如购买商品需要支付,你可以提供 微信支付.支付宝支付.支付通支付....(不同的支付方式就是不同的策略) 例2:购物车对产品的计价,如非vip 按原价计算 .vip按8折计算.有推广积分的可以用100积分抵20块...(不同的客户条件计价算法有所不同,只是这里的策略选择是根据登录顾客资料来变动的,当然也可以用观察者模式

  • Java如何利用策略模式替代if/else语句

    平时在开发中避免不了使用大量的if else语句,但过多层的if else对于性能有很大的开销,类似如下代码 public class MainStart { public static void main(String[] args) { String msgid = "MS066"; if(message.equals("MS066")){ System.out.println("MS066"); }else if (message.equa

  • 详解Python设计模式之策略模式

    虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用.<设计模式:可复用面向对象软件的基础>一书中有 23 个模式,其中有 16 个在动态语言中"不见了,或者简化了". 1.策略模式概述 策略模式:定义一系列算法,把它们一一封装起来,并且使它们之间可以相互替换.此模式让算法的变化不会影响到使用算法的客户. 电商领域有个使用"策略"模式的经典案例,即根据客户的属性或订单中的商品计算折扣. 假如一个网店制定了下述折扣规则. 有 1000 或

  • Java策略模式实现简单购物车功能

    策略模式是一种行为模式.用于某一个具体的项目有多个可供选择的算法策略,客户端在其运行时根据不同需求决定使用某一具体算法策略. 策略模式也被称作政策模式.实现过程为,首先定义不同的算法策略,然后客户端把算法策略作为它的一个参数.使用这种模式最好的例子是Collection.sort()方法了,它使用Comparator对象作为参数.根据Comparator接口不同实现,对象会被不同的方法排序. 本文例子是,完成一个简单地购物车,两种付款策略可供选择,一为信用卡,另外一种为Paypal. 首先创建策

  • Java利用策略模式优化过多if else代码

    前言 不出意外,这应该是年前最后一次分享,本次来一点实际开发中会用到的小技巧. 比如平时大家是否都会写类似这样的代码: if(a){ //dosomething }else if(b){ //doshomething }else if(c){ //doshomething } else{ ////doshomething } 条件少还好,一旦 else if 过多这里的逻辑将会比较混乱,并很容易出错. 比如这样: 摘自cim中的一个客户端命令的判断条件. 刚开始条件较少,也就没管那么多直接写的:

  • JavaScript设计模式之策略模式实现原理详解

    俗话说,条条大路通罗马.在现实生活中,我们可以采用很多方法实现同一个目标.比如我们先定个小目标,先挣它一个亿.我们可以根据具体的实际情况来完成这个目标. 策略模式的定义 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 举个例子:表单校验 在一个Web项目中,注册.登录等功能的实现都离不开表单提交.表单校验也是前端常常需要做的事.假设我们正在编写一个注册的页面,在点击提交按钮之前,有如下几条校验逻辑: 用户名不可为空,不允许以空白字符命名,用户名长度不能小于2位. 密码长度不能小

  • 详解SpringBoot结合策略模式实战套路

    1.1. 前言 我们都知道设计模式好,可以让我们的代码更具可读性,扩展性,易于维护,但大部分程序猿一开始都学过至少一遍设计模式吧,实战中不知用到了几成.接下来让我介绍一个结合SpringBoot的策略模式套路,让你的代码少些if-else 1.2. 开撸 废话不多说,直接告诉你今天的核心是@autowired,看到这个是不是很熟悉,你每天都在用,不就是自动注入Spring管理的Bean吗?但我们对它的用法很多时候就局限在全局变量的注入了,忘记了,它其实还可以构造器注入,类型注入或命名注入,那么结

  • Spring 环境下实现策略模式的示例

    背景 最近在忙一个需求,大致就是给满足特定条件的用户发营销邮件,但是用户的来源有很多方式:从 ES 查询的.从 csv 导入的.从 MongoDB 查询-.. 需求很简单,但是怎么写的优雅,方便后续扩展,就存在很多门道了. 我们的项目是基于 Spring Boot 开发的,因此这篇文章也会基于 Spring Boot 作为基础框架,教你如何使用 Spring 依赖注入的特性,优雅的实现策略模式. 1. 简单粗暴 最简单粗暴直接的方式莫过于 if...else- 了,伪代码如下: if(来源 ==

  • java设计模式策略模式图文示例详解

    目录 策略模式 意图 问题 解决方案 真实世界类比 策略模式结构 伪代码 策略模式适合应用场景 实现方式 策略模式优缺点 策略模式优缺点 与其他模式的关系 策略模式 亦称:Strategy 意图 策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换. 问题 一天,你打算为游客们创建一款导游程序.该程序的核心功能是提供美观的地图,以帮助用户在任何城市中快速定位. 用户期待的程序新功能是自动路线规划:他们希望输入地址后就能在地图上看到前往目的

  • MyBatis在Spring环境下的事务管理

    MyBatis的设计思想很简单,可以看做是对JDBC的一次封装,并提供强大的动态SQL映射功能.但是由于它本身也有一些缓存.事务管理等功能,所以实际使用中还是会碰到一些问题--另外,最近接触了JFinal,其思想和Hibernate类似,但要更简洁,和MyBatis的设计思想不同,但有一点相同:都是想通过简洁的设计最大限度地简化开发和提升性能--说到性能,前段时间碰到两个问题: 1.在一个上层方法(DAO方法的上层)内删除一条记录,然后再插入一条相同主键的记录时,会报主键冲突的错误. 2.某些项

  • RestTemplate在Spring或非Spring环境下使用精讲

    目录 一.什么是RestTemplate? 二.非Spring环境下使用RestTemplate 三.Spring环境下使用RestTemplate 一.什么是 RestTemplate? RestTemplate是执行HTTP请求的同步阻塞式的客户端,它在HTTP客户端库(例如JDK HttpURLConnection,Apache HttpComponents,okHttp等)基础封装了更加简单易用的模板方法API.也就是说RestTemplate是一个封装,底层的实现还是java应用开发中

  • PHP实现的策略模式简单示例

    本文实例讲述了PHP实现的策略模式.分享给大家供大家参考,具体如下: 比如说购物车系统,在给商品计算总价的时候,普通会员肯定是商品单价乘以数量,但是对中级会员提供8者折扣,对高级会员提供7折折扣,这种场景就可以使用策略模式实现: <?php /** * 策略模式实例 * */ //抽象策略角色<为接口或者抽象类,给具体策略类继承> interface Strategy { public function computePrice($price); } //具体策略角色-普通会员策略类 c

  • Spring Cloud下OAUTH2注销的实现示例

    接上文Spring Cloud下基于OAUTH2认证授权的实现,我们将基于Spring Cloud实现OAUTH2的注销功能. 1 增加自定义注销Endpoint 所谓注销只需将access_token和refresh_token失效即可,我们模仿org.springframework.security.oauth2.provider.endpoint.TokenEndpoint写一个使access_token和refresh_token失效的Endpoint: @FrameworkEndpoi

  • .NET 下运用策略模式(组合行为和实体的一种模式)

    我简单的理解策略模式就是把行为(方法)单独的抽象出来,并采用组合(Has-a)的方式,来组合行为和实体的一种模式.再来个官方的解释: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. 网上也有很多资源介绍这个模式,我也不从头说起了.在.

  • java实现策略模式使用示例

    思路如下: 使用interface来定义一个接口,在该接口中定义save()方法:根据图片格式定义不同的类,分别在这些类中使用关键字implements实现接口:创建一个实现选择的类,在该类中定义实现选择的方法,该方法返回值为对应的图片保存类:在主方法中实现接口.代码如下: 复制代码 代码如下: public interface ImageSaver {    void save();//定义save()方法} public class GIFSaver implements ImageSave

  • 详解JavaScript的策略模式编程

    我喜欢策略设计模式.我尽可能多的试着去使用它.究其本质,策略模式使用委托去解耦使用它们的算法类. 这样做有几个好处.他可以防止使用大条件语句来决定哪些算法用于特定类型的对象.将关注点分离开来,因此降低了客户端的复杂度,同时还可以促进子类化的组成.它提高了模块化和可测性.每一个算法都可以单独测试.每一个客户端都可以模拟算法.任意的客户端都能使用任何算法.他们可以互调.就像乐高积木一样. 为了实现策略模式,通常有两个参与者: 该策略的对象,封装了算法. 客户端(上下文)对象,以即插即用的方式能使用任

随机推荐