阅读 3561

巧用策略模式完美应付产品四次需求变更,也吵了四次|2021 年中总结

前言

  • 设计模式大家应该很熟悉了,使用最多的应该就是工厂模式。关于工厂模式有简单工厂、懒汉工厂、饿汉工厂等等形式,今天我们结合项目场景来总结下策略模式

项目需求

image-20210615101701014

  • 上面是我们需求效果图!我们需要针对个人对本年度指标完成情况进行一次统计。
  • 比如上面test用户在xxx年份中有5个指标考核。每个指标考核维度不一样,对于指标1考核目标是一天施工一次,然后对一个月进行汇总考核有点类似于上班打卡的形式。而他打卡的方式就是后方的施工按钮。对于指标1他只需要每天点击施工填写响应的数据即可!关于施工后填写数据就是一份表单数据这个是非常简单的。
  • 包括后面1月1次、1季度1次、半年1次、1年1次这些考核频率都是同样的操作。这份报表统计应该也很容易没啥难点。我们只需要在生成的时候按照月份查找施工记录是否符合当前频率要求!

image-20210615111422888

  • 在项目中对频率进行判断。不同的频率我们做不一样的逻辑就完事了。这个可以说是初级程序!!!这样完全没有考虑到语言或者说设计模式。这种思路实现完全没有问题但是没有考虑到后续的扩展性!
  • 首先代码会变得很长不利于他人阅读理解。其次是没有做到相互隔离如果其中一个分支出现问题需要进行修改这时候对其他的分支实际上是一次污染,因为他们都在一起,因为修改某个分支从而导致其他分支异常的情况是经常出现的。这种做法也是我之前最喜欢的方式---不知道已设计模式的角度考虑问题

瓶颈突破

  • 成长是一条艰辛的道路,但是对于别人来说可能就是那么一瞬间!初级成长成中级程序员我们需要多少努力只有经历之后才知道但是对别人来说可能会认为是一次代码的事情!今天我就抛弃到传统的流水账形式开发,结合项目实战运用起设计模式。
  • 经过仔细分析了之后觉得设计模式中的策略模式很是适合该需求。我们可以将频率抽象成策略 ,我们先来看看下面我的图示

image-20210615111158546

  • 首先将策略进行抽象成AbstrachTimeStrategy 该类主要责任就是对数据进行分析,接收两个参数分别是Map , bean 。前者是用户月份详情数据,后者是根据频率相关的解析数据。在每个策略中针对单一的场景进行具体分析,每个类具有了单一的职责这样将他们进行分离,比如说上面PerHalfSomeTimesStrategy出现的bug那么我们只需要对他进行修复,其他的策略是不会受影响的。
  • 这是从宏观层面进行相互隔离。我们进入到每个策略中也是很简单了。每个策略我们只需要定制化的考虑具体的场景。比如PerDaySomeTimesStrategy策略对应的一天N次的频率。我们在里面只需要获取到指定月份的施工记录并对这些记录按天分组,最终看看每天的完成情况是否满足我们N次的要求就可以了。而不需要去考虑季度、年的频率问题
  • 运用策略模式还有一个好处是我们可以随意的装配!如果现在我们的需求是多个频率只需要满足其中一个就可以了。那么我们只需要将多个频率对应的策略进行分析下就可以得出结论!如果是之前流水账形式的开发的话我们肯定是需要将代码进行重构下!说的好听的是重构,难听的就个话其词了。
  • 当然除了策略以外,我们还需要一个工厂类。这个工厂类是做啥子用的呢?因为策略本身只需要考虑策略的场景。至于谁去组装这些策略那就是我们的工厂的活了。

策略组装

image-20210615114206964

  • 首相该工厂提供了注册器和获取对应策略的两个入口。另外注册器内部是将具体策略已别名的方式注册到内部的一个map中。为了方便我直接在工厂中添加了目前我们需求确定的策略。后续如果需求变动我就不在对工厂进行修改而是直接通过注册器向内部进行注册新的策略。
static {
    strategyMap.put("month", new PerMonthSomeTimesStrategy());
    strategyMap.put("halfMonth", new PerHalfMonthSomeTimesStrategy());
    strategyMap.put("year", new PerYearSomeTimesStrategy());
    strategyMap.put("day", new PerDaySomeTimesStrategy());
    strategyMap.put("squash", new PerSquarshSomeTimesStrategy());
    strategyMap.put("halfyear", new PerHalfYearSomeTimesStrategy());
}
复制代码
  • 上述就是我们系统内部确定的6中策略。剩下的就是我们在系统需要借助策略分析的时候通过工厂的获取策略器进行分析
TimeStrategy strategy = ContextTimeStrategyFactory.getInstance().getStrategy(cycle.getCycleName());
复制代码
  • 这样子的代码看起来真的很舒服!就算是其他人来阅读也很容易理解。而且还用担心其他接手的XD改出问题!想一想当我将内置的功能如果进行maven进行分模块开发那么其他人不论怎么修改都不会影响到我们已有的6中策略了。设计模式真的是我们程序的救星啊!

需求变更

  • 实际开发中面对需求变更我们程序员都是如何面对的呢?不管怎么面对最终只能默默的接受!上面我们的需求是统计个人指标完成情况。针对该需求我特意仔细研究了一下最终决定使用策略模式。策略模式的好处就是方便扩展,这么我们的产品又提出一种方式需要按照周的维度去考核完成情况。接到这个需求我是悲喜各占一半吧!假装性的吐槽了下产品为什么没有在初期确定好需求。但是实际上在策略模式的基础上还是很好扩展这个功能的。

image-20210615133219606

  • 对!就是这么简单我们只需要实现一个继承了抽象算法类的接口,然后将新实现的算法注册到工厂内部map中。然后就会在对应的频率中使用到我们的新的策略!我也是嗖嗖嗖嗖的进行了升级不一会就完成了!本次需求的变更完全是意料之中的升级!交工之际还不忘感慨下自己的明智!!

需求白热化

落地

  • 有时候一度认为产品就是程序员的天敌。他们会无时无刻折磨你!这不刚刚新增了一个策略需求还没等你缓过来需求又改动了!我也只能兵来将挡水来土掩!
  • 之前是在月末进行统一计算,因为用户对数据没有实时性要求所以我们是定时器在每月末进行计算的。现在需求需要做到计算是实时计算的。这就让我这个本不富裕的程序员更加的雪上加霜了!这不是逼着我加班吗?下面就开始了我和产品之间的【火热讨论】了。作为弱势群体最终结果可想而知
  • 那么我们在对需要进行一次分析。我们的考核是针对个人在指定周期内的,所以想要实现实时的那么我们就得监控到施工记录中的数据。根据施工中变动的数据决定对哪些人进行重新计算考核情况

image-20210615135557541

  • 施工记录中张三数据发生了新增、修改、删除等操作都会影响到我们考核详情中张三对应的数据情况。我们根据姓名能够匹配到考核对象在根据张三施工记录这条数据的时间可以匹配到具体会影响到哪一月的数据;根据这些信息我们能够精准定位到数据。
  • 定位到数据之后我们就需要对数据进行更新!这涉及到到两种方式:增量+全量
  • 如果是增量我们需要区分是新增、修改、删除的情况在对考核完成数量进行响应的增加、减少;全局考虑一下增量是最适合的方式因为这样计算量是最小的但是对于我们的编程工作量确很大!作为资深摸鱼师我果断放弃了这种方式,选择全量模式
  • 全量模式相对简单很多!我们只需要检测到对应的人员的时间发生施工变化时我们就重新计算该人员的月份下的完成数据!当然我也不是完全为了自己方便选择全量模式的。也是结合了数据量的大小决定的。因为实际生产中一个月的数据不会过200!这个是分析了客户之前的历史数据得出的结论!数据是不会说谎的。

增量

  • 虽然最终没有选择增量,但是在我还是实现了增量模式的骨架!也是为了方便以后扩展。如果日后数据量出现激增的情况增量就是最优方式。关于增量上面说了需要考虑【施工记录】的新增、修改、删除的情况。而策略模式就是为什么解决我们的分支判断的。所以这里显然又是一个策略模式

image-20210615140701908

  • 同样的配方、同样的开发人员我们就是这么任性!通过策略模式工程就可以提供出具体的策略来实现了。剩下的就是在数据新增时通过工厂获取到InsertDataHandlerStrategyImpl算法去解析就行了。

  • 每种策略计算出数据之后根据策略的特性在原有基础上对完成情况进行加减就可以了。

  • 封装好方法之后就开始找负责【施工记录】的同学麻烦他们在响应的方法上添加策略的调度!

解耦

  • 上面不管是增量还是全量都需要【施工记录】的同学对我们模块进行通知!当时找他们的时候也是抱怨不断!抱怨我们总是需求不定给他们带来很多不必要的工作量!
  • 我本人很是赞成他们的抱怨,回来也和我们的产品反应了情况。我是两头受气产品的意见就是这个需求必须做必须准时上线!又是一个黑夜,又是一杯茶一包烟陪我度过。最终我决定使用消息队列来进行解耦这样对【施工记录】的同学来说工作量最小而且日后我们也会摆脱对他们的要求。
  • 最终就是让【施工记录】同学在对施工记录进行增删改查的时候想消息队列中心中投递消息!我们模块在订阅消息,接收到消息后我们根据消息中约定好的类型就可以知道是新增还是修改还是删除了。然后我们就可以调用对应的策略进行增量数据修复了。或者说直接调用全量模式方法进行数据重新计算了。不管怎么样接收到消息之后就是我们自己内部组装的事情了。
  • 而且后续我们还可以基于订阅的消息在进行开发其他的功能!真实一举两得。

我已阵亡

  • 添加周频率考核目标、实时计算考核两个需求的变动已经让我暴跳如雷了。产品又抛出一个需求需要清洗历史数据!理由是项目上线客户需要维护历史数据
  • 不过这个需求我觉得还是很合理的。所以默默地又是自己背下了所有。针对历史数据清洗准确的说也是自己没有考虑那么长远既然需求来了而且很合理那就开始做吧
  • 本次变动不需要我们在架构上有啥改动。唯一改动的就是之前的策略算法。通过消息队列接收到变动后需要计算当前时间和考核时间的关系如果确定是历史数据的则会认定是延期状态。这里还涉及到按季度、半年、年等跨多月的情况。如果是跨多月恰巧数据不再考核月,我们还需要对月份进行纠偏操作。

image-20210615142500178

  • 这样客户在当前时间上传之前月份的数据时状态也会重新计算的。

新增考核依据

  • 所以说永远不能相信产品说的话!这么现在又想修改考核依据。不在已【施工记录】为最终考核依据而是需要将考核依据与指标项进行绑定。也就是说每个指标对应自己的一个考核依据我们需要根据对应的考核依据进行计算!
  • 辛苦之前我和【施工记录】的同学们约定了将操作放到消息队列中!并在整个部门中进行同样的约定。现在的改动就不需要别人配合了。我直接从消息队列中监听相关的考核依据表然后在查出对应指标项就可以了。后面重复我们之前的全量模式和具体的频率策略计算就可以了

项目总结

  • 本来是个简单的功能由于一开始采用的了对的选择,所以在需求不断变更时基本上我还是在不大改代码的情况下完成了功能的升级的!

  • 而且每次需求变动基本上也是对自己当初定下的骨架进行不断的功能完善的。并没有出现因为需求变动导致骨架重新设计的场景。这也足够说明当初选择策略模式的正确性

  • 策略模式其实就是对算法的一种抽象!策略模式往往需要结合工厂模式。每种策略实现的功能是一样的像上述的场景每种策略都是对考核月数据进行考核只不过每个策略的考核依据不同而已。而策略本身值关注算法的实现而不考虑调用。所以借助于工厂来进行维护策略,方便第三方进行策略的调度!

  • 策略模式的理解最好的就是商品售价的落地实现了。商场经常会有各种各样的打折优惠有的甚至是叠加优惠。这种场景我们将各种的优惠抽象成策略我们在工厂中将各种的策略叠加使用就是平时的商品促销了!

  • 文章中也有提到每种策略只需要关心自己本身!也就是说在开发中每种策略我们可以交由不同的开发者去实现!开发者只需要自测自己的策略没问题就可以了!在架构中我们甚至可以将策略模式运用横跨语言,每种实现甚至可以交由不同语言去实现!

  • 那么什么时候应该用策略模式呢?简单的理解就是如果你的代码中出现分情况的场景,就该策略模式出马了

总结

  • 本文通过项目实战的角度应该可以说是透彻的解析了策略模式的场景!之前不想写关于策略模式在项目中的运用的。经过几次的需求变更越来越觉得当初的选择真的明智!虽然现在说起来面对需求变动感觉改的很轻松!但是当时真的是很艰难有的时候实在没办法硬逼着自己改造的。最终才实现出相对满意的模块功能。
  • 经过这个功能的开发慢慢的自己成长很多!不在是串行编程,懂得将特性就行抽象化开发这无疑是对开发者一种质的提升!面对需求的变更我不在去关注代码本身,而是将重心放在项目骨架上!只有骨架满足需求的情况下代码的开发才会变得有意义!
  • 经过一个项目的开发让我学到了设计模式并非理论!他是前人在无数个项目实战中落地总结!善用设计模式可以避免掉很多无意义加班!可以说如果不用设计模式这个项目面对多次需求变更洗礼加班不会比现在少的。而且最终成品应该也只有我自己能够看懂了。用了设计模式就不一样了,后面接手的同学应该也能够理解到我们思路,思路清晰的同学应该可以理解到当初项目需求的变更记录吧
  • 年中将至,关于个人经历总结之前已经专门发了一篇文章陈述了自己这半年以来的成长!而此篇是从技术成长的角度介绍了自己2021的收获。

点赞、关注、产生共鸣!!!

掘金年中主题活动 | 2021 我的半程成长之路征文活动正在进行中......

文章分类
后端
文章标签