抽象类、策略模式、工厂模式在业务设计中的应用实践

一 抽象类和设计模式在实际业务中的使用

1.1 场景

平时常见的后台管理系统,系统运行时会有各种数据,例如:登录站点的IP量、交易产生的流水、给注册新号的用户发送了多少次验证码等等,很多指标都需要后台程序监测;后台管理员可以设置自己想要查看的指标,后台会根据设置的指标类型进行统计;管理员可以给指标的统计结果设置一个阈值,一旦该指标统计数值超过阈值,会认为数据有异常,然后给予管理者直观的提醒;

场景分析:

其实主要就两点:
1 随着项目越来越大,业务越来越多,可以监测的指标真的很多。这里假设目前要监测100个指标;想想平时游戏的趣味统计数据,就是服务器后台在悄悄统计;
2 需要判断指标是否超出设置阈值,超出则需要告知管理员;

2 实际写代码中遇到的痛点?

某科技公司近期就要做一个后台监测程序。在一次需求分析会上,团队领导们经过一番讨论,最终决定,将这个任务交给新来公司一个月的张三去做,锻炼锻炼张三的代码能力,这也是给新人展示自己的机会嘛。

张三心里暗道不妙,这个需求有点难搞,自己水平还是不高,怎能按时将需求搞掉,yo!

于是张三会后对这个业务进行了一波分析推理:

2.1 首先,怎么统计?为何需要抽象类?

应该有如下三步:

1 构建DTO,方便去各个微服务模块统计数据;考虑到是统计,常见的变量包括:开始结束时间、指标的唯一ID;
2 真正的去调用其他微服务,统计数据;
3 对统计结果进行分析和持久化,或者放入缓存中;

public class StatisticsServiceImpl implements StatisticService {

   @Resource
   private XXXXService xxxxService;
   
   @Override
   public void doStatistics(Object o){
       //构造查询用的对象
       xxxxService.createObjectToStatistics(....);

       //去做查询工作;
       doStatistics(....);

       //将查询的结果进行处理,比如分析、存储到表中等等;
       xxxxService.doAnalysisAndPersistence();
   }

}

100个指标,如果直接用上面的方法,会发现每个指标类还是有共性可以抽取;首先,构造查询用的DTO时,其实内部成员变量往往都是相近的参数,比如时间段,所以第一步算是共性部分;然后,对统计结果做分析判断,对有异常的数据要通知管理员,然后将记录添加到数据库中,这个过程都是一样的,所以也可以提取共性;

而且你会有一种感觉,明明自己只是想统计下数据,重要的是如何去获取到想要监测的数据结果,你只想做好第二步,但是每一个统计类都要重复以上三个步骤;

所以,可以用抽象模板类解决;

2.2 怎么运行这些统计?或者说,怎么根据管理员的设置,把其想要的指标都统计起来?–》 设计模式的运用

后台管理员可以设置多个指标,这些指标的唯一标识传进来,我们就得用if else 或者switch去判断,然后调用对应的统计实例去统计;实际操作的时候,这里的判断会很繁琐 ----》运用策略模式,根据这个唯一标识直接获取所需的统计类;

运行统计需要用到统计类实例,然后调用其统计方法;但是我们其实不想关心怎么一个一个创建具体的统计实体类,这很麻烦,也容易犯错,耦合性还高,我们只是想调用其统计方法;----》工厂模式创建,屏蔽创建细节;

工厂模式、策略模式的概念和用法可以看下面这篇文章的说明:
设计模式在外卖营销业务中的实践
设计模式之工厂模式(factory pattern) - alpha_panda - 博客园 (cnblogs.com)

那么在这个业务里,怎么设计呢?

1 根据某个唯一值可以确定对应的策略,由工厂模式+策略模式生成该策略;所有的统计类可以统一维护在一个缓存中,或者利用spring也是可以的;
2 定义统计接口,所有统计指标的类实现该统计接口;
3 后台定时任务遍历所有统计类,统计指标的值;

这样设计,可以让每个策略接口专注于指标的统计;同时,如果新加了指标,只需加一个类即可,扩展性好。
严谨的说法,叫:符合开闭原则、单一职责原则、里氏代换原则、依赖倒转原则、接口隔离原则、迪米特法则;

其中,里氏代换原则参考: (28条消息) 里氏代换原则 举例 分析_xie__jin__cheng的博客-CSDN博客_里氏替换原则
七大原则中六个原则参考: (28条消息) Java中常用的设计模式_Superme-CSDN博客_java设计模式
七大原则参考: (28条消息) 设计模式7大原则_mindcarver-CSDN博客

3 示例:

3.1 首先定义一个策略接口,这个接口有两个方法,1 定义统计流程 2 真正做统计工作的方法

public interface StatisticsStrategy {

   /**
    * 统计数据的全部流程
    * @param id 唯一id
    */
   void countData(Long id);

   /**
    * 真正去统计数据的方法
    * @param dto dto
    * @return 统计结果
    */
   Object doCountData(Object dto);
}

3.2 用抽象类去实现接口,但是不完全实现接口的所有方法。前面说过,要抽取共性部分,而接口中的doCountData是每个策略都不同的,但是统计流程中有很多部分相同,所以抽象类只实现countData方法;

public abstract class BaseStatisticsStrategy implements StatisticsStrategy{

   @Resource
   private AnalyzeAndPersistenceService analyzeAndPersistenceService;

   @Override
   public void countData(Long id) {

       Object dto = constructDTO(id);

       Object countResult = doCountData(dto);

       analyzeAndPersistenceService.analyzeAndPersistence(countResult);
   }

   private Object constructDTO(Object dto) {

       //构建DTO
       //......
       return null;
   }

   private void analyzeAndPersistenceData(Long id) {

       //分析和持久化数据
   }
}

3.3 对于每个策略实现类,只用关心自己的指标如何统计即可:

@Component
public class SimpleStatisticsStrategy extends BaseStatisticsStrategy {

   @Resource
   private CountService countService;

   @Override
   public Object doCountData(Object dto) {

       return countService.count(dto);
   }
}

4 总结

张三按照上面的设计,提交代码之后,瞬间神清气爽,这样写剩余的指标,只用专注于统计指标就好了,不用每次都要复制粘贴,工作量减少,心情大好;

领导看到张三的设计,对张三进行了表扬;张三很谦虚的说,哪里哪里,基本操作;

其他同事:可恶,让张三装到了。

二 使用时考虑的问题:

1 如果抽象类内部需要用到bean,如何进行依赖注入?

如上面的代码所示,直接用@Autowired或@Resource 注入即可;

这里有个疑问,抽象类都没有添加@Component注解,spring扫描不到,怎么注入依赖?
答:其子类有@Component,为子类注入时,其父类需要的依赖也会被注入;

2 如果 用到了事务,什么情况会使事务失效?try catch与声明式事务的冲突?

先说spring声明式事务会失效的几种情况,具体可参考:
@Transactional 注解失效场景-布布扣-bubuko.com

这里就说几个业务中一定要注意的失效场景:

1)方法没用public修饰;
注意下即可;

2)同一个类中方法调用;
这个一定要注意。什么事方法内调用,如下:

public abstract class BaseStatisticsStrategy implements StatisticsStrategy{

   @Override
   public void countData(Long id) {

       Object dto = constructDTO(id);

       Object countResult = doCountData(dto);

       analyzeAndPersistence(countResult);
   }

   private Object constructDTO(Object dto) {

       //构建DTO
       //......
       return null;
   }

   @Transactional(rollbackFor = Exception.class)
   public void analyzeAndPersistenceData(Long id) {

       //分析和持久化数据
   }
}

分析和持久化,可能涉及很多写操作,可能会在这里加事务。
但是这个方法会被本类中countData调用,这就属于同一个类中方法调用,此时事务是不生效的;

要想生效,可以将事务加在countData方法上,而countData方法是被外部调用的,所以会生效;

3)注意方法内部的try…catch;

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,
事务是否回滚,取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。
在业务方法中一般不需要catch异常,如果非要catch一定要抛出throw new RuntimeException(),否则会导致事务失效,数据commit造成数据不一致,所以有些时候try catch反倒会画蛇添足。

2.3 抽象类可以实现接口吗?怎么配合才可以有好的效果?

肯定是可以的,上面的代码里都用到了。
抽象类实现接口时,可以只实现部分接口的方法,甚至不实现接口方法,没有任何的问题;

那为什么需要部分实现?
就和上面示例中所展示的一样,每个策略不需要完全实现所有接口方法,而模板类也不需要统计所有的指标。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值