• 知道策略模式!但不会在项目里使用?


    前言

    在学开发的第二年就开始听说要想代码写得好,一定要会设计模式。于是就兴致冲冲的啃了《Head First 设计模式》,看完之后对于策略模式映像很深刻,觉得这个模式好,易上手,应用广,我又能优化一波代码了(装波逼了),于是兴致冲冲的打开了我的 IDEA,开整!!!

    策略模式初体验(错误示范)

    在讲诉我的策略模式首秀前,我们先回顾下策略模式的基本概念。

    策略模式

    • 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
    • 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
    • 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

    简单的来说当做某个事情有多个方式的时候,可以抽象为接口,然后每个实现是一种解决方式,由调用方来选择不同的实现方式。

    理解了后我开始对我们的代码进行了重构,当时我第一家公司有这样一段代码,大概是这个意思(时间长了,我凭记忆重写的)。

    有这样一个抽奖的方法,我们后台控制中奖率,不同的时候我们会调整不同的中奖策略。

    1. public class NumStrategy {
    2. enum RandomEnum{
    3. /**
    4. * 平均策略
    5. */
    6. AVERAGE,
    7. /**
    8. * 80%的几率中奖
    9. */
    10. RANDOM28;
    11. }
    12. /**
    13. * 抽奖方法,根据不同的策略进行抽奖
    14. * @param randomEnum
    15. * @return true:代表中奖 false:代表没中奖
    16. */
    17. public boolean luckDraw(RandomEnum randomEnum){
    18. if(randomEnum.equals(RandomEnum.AVERAGE)){
    19. Random random = new Random();
    20. int num = random.nextInt(100);
    21. return num >= 50;
    22. }else if(randomEnum.equals(RandomEnum.RANDOM28)){
    23. Random random = new Random();
    24. int num = random.nextInt(100);
    25. return num >= 20;
    26. }
    27. return false;
    28. }
    29. }
    30. 复制代码

    我一看,这不就是妥妥的策略模式吗?开搞。

    一顿改造之后变成了这样:

    1. public class NumStrategy2 {
    2. enum RandomEnum{
    3. /**
    4. * 平均策略
    5. */
    6. AVERAGE,
    7. /**
    8. * 80%的几率中奖
    9. */
    10. RANDOM28;
    11. }
    12. /**
    13. * 抽奖方法,根据不同的策略进行抽奖
    14. * @param randomEnum
    15. * @return true:代表中奖 false:代表没中奖
    16. */
    17. public boolean luckDraw(RandomEnum randomEnum){
    18. if(randomEnum.equals(RandomEnum.AVERAGE)){
    19. return new AverageStrategy().luckDraw();
    20. }else if(randomEnum.equals(RandomEnum.RANDOM28)){
    21. return new Random28Strategy().luckDraw();
    22. }
    23. return false;
    24. }
    25. interface LuckDrawStrategy{
    26. boolean luckDraw();
    27. }
    28. class AverageStrategy implements LuckDrawStrategy{
    29. @Override
    30. public boolean luckDraw() {
    31. Random random = new Random();
    32. int num = random.nextInt(100);
    33. return num >= 50;
    34. }
    35. }
    36. class Random28Strategy implements LuckDrawStrategy{
    37. @Override
    38. public boolean luckDraw() {
    39. Random random = new Random();
    40. int num = random.nextInt(100);
    41. return num >= 20;
    42. }
    43. }
    44. }
    45. 复制代码

    改造完成之后我满意的提交了代码,但是在组长 review 的时候给我又改了回来。说你整这么多类干嘛?我理直气壮的说我这是用策略模式优化代码。他说没必要,先改回去吧。

    我愤愤的接受了,但心里想着:哎,你连策略模式都不懂?

    经过这么多年,我开始理解我当时的做法其实不对,本来很简单的代码,而且里面的逻辑不会有变动,其实不需要抽象出来。我的改动有过度设计之嫌。把原来的 30 行代码搞成了 80 行

    一报还一报,这几年我见过太多次当年的我这样写代码的了。

    为了用设计模式而用设计模式。而忘了设计模式的初衷是为了代码更易理解,更可靠,更易维护

    甚至还见过有人学了策略模式后说要把项目里所有的 if/else 都安排上策略模式。

    梅开二度

    又过了一年多,在一次面试的时候,也有着关于策略模式的讨论。

    【面试官】问:你说你用过策略模式,请问你为什么用它?

    【我】:为了抽离各个不同实现逻辑,优化 if/else,使代码更简单易懂

    【面试官】:你具体说说,怎么去掉的 if/else

    【我】:内心 OS(背的知识点,我也好久没用了啊)。我硬着头皮说,我可以使用工厂模式+策略模式来做。

    【面试官】:那你工厂模式的那里不是也要用 if/else 判断吗?

    【我】:。。。额。唔。。。那确实还是要用到 if/else

    把我问住了,我支支吾吾的回答确实还是要 if/else 来判断一次,只不过把判断移到了工厂模式里面去了。

    我下来后又去实践了下,想着放在 map 里行不行呢?

    1. public class NumStrategy3 {
    2. enum RandomEnum{
    3. /**
    4. * 平均策略
    5. */
    6. AVERAGE,
    7. /**
    8. * 80%的几率中奖
    9. */
    10. RANDOM28;
    11. }
    12. static Map<RandomEnum,LuckDrawStrategy> map = new HashMap<>();
    13. static{
    14. map.put(RandomEnum.RANDOM28,new Random28Strategy());
    15. map.put(RandomEnum.AVERAGE,new AverageStrategy());
    16. }
    17. /**
    18. * 抽奖方法,根据不同的策略进行抽奖
    19. * @param randomEnum
    20. * @return true:代表中奖 false:代表没中奖
    21. */
    22. public boolean luckDraw(RandomEnum randomEnum){
    23. LuckDrawStrategy luckDrawStrategy = map.get(randomEnum);
    24. return luckDrawStrategy.luckDraw();
    25. }
    26. interface LuckDrawStrategy{
    27. boolean luckDraw();
    28. }
    29. static class AverageStrategy implements LuckDrawStrategy{
    30. @Override
    31. public boolean luckDraw() {
    32. Random random = new Random();
    33. int num = random.nextInt(100);
    34. return num >= 50;
    35. }
    36. }
    37. static class Random28Strategy implements LuckDrawStrategy{
    38. @Override
    39. public boolean luckDraw() {
    40. Random random = new Random();
    41. int num = random.nextInt(100);
    42. return num >= 20;
    43. }
    44. }
    45. }
    46. 复制代码

    终于是解决了 if/else 的情况,不过这样很短的 if/else,里面逻辑不怎么变动时,我个人是不建议用策略模式,这里只是示例。

    推荐用法

    又过了几年,当初的菜鸟也成长为了一个老鸟。

    当时项目里有这样一个代码:

    下面的代码我进行了一些简化,我们有一个功能,对页面上的指标进行计算,不同的指标对应不同的计算方法。页面上指标一期做 4 个,后续会做到十几个。

    1. public interface TransferService {
    2. String transfer();
    3. }
    4. @Service
    5. public class SearchTransformService {
    6. @Autowired
    7. private UserTransferService userTransferService;
    8. @Autowired
    9. private AgeTransferService ageTransferService;
    10. @Autowired
    11. private InterestTransferService interestTransferService;
    12. /**
    13. * 根据不同的编码进行转换
    14. * @param code
    15. * @return
    16. */
    17. public String transform(String code){
    18. if(code.equals("user")){
    19. return userTransferService.transfer();
    20. }else if(code.equals("age")){
    21. return ageTransferService.transfer();
    22. }else if(code.equals("interest")){
    23. return interestTransferService.transfer();
    24. }
    25. return "";
    26. }
    27. }
    28. 复制代码

    可以看到这样的业务场景下,这样的写法 if/else 就会很长,后续十几个的情况下就很难维护。另外 code 使用的是魔数,也是不好的一种写法。我对此进行了优化如下:

    1. 先将 code 用枚举定义
    1. enum CodeEnum {
    2. USER("user"),
    3. AGE("age"),
    4. INTEREST("interest"),
    5. ;
    6. private String code;
    7. public String getCode() {
    8. return code;
    9. }
    10. CodeEnum(String code) {
    11. this.code = code;
    12. }
    13. private static final Map<String, CodeEnum> map = Arrays.stream(CodeEnum.values()).collect(Collectors.toMap(CodeEnum::getCode, Function.identity()));
    14. public CodeEnum of(String code) {
    15. return map.get(code);
    16. }
    17. }
    18. 复制代码
    1. 原有的接口上增加一个 transCode 方法,每个实现需要声明是对应哪个编码的实现
    1. public interface TransferService {
    2. String transfer();
    3. CodeEnum transCode();
    4. }
    5. @Service
    6. public class AgeTransferService implements TransferService {
    7. @Override
    8. public String transfer() {
    9. return null;
    10. }
    11. @Override
    12. public CodeEnum transCode() {
    13. return CodeEnum.AGE;
    14. }
    15. }
    16. 复制代码
    1. 使用 map 存储编码对应的实现类的关联关系,以此来获取对应的转换器实现类
    1. @Service
    2. public class SearchTransformService implements InitializingBean {
    3. @Autowired
    4. private List<TransferService> transferServiceList;
    5. private Map<CodeEnum, TransferService> transferServiceMap;
    6. @Override
    7. // 项目启动时将实现类放入到map中去
    8. public void afterPropertiesSet() throws Exception {
    9. transferServiceMap = transferServiceList.stream().collect(Collectors.toMap(TransferService::transCode, Function.identity()));
    10. }
    11. /**
    12. * 根据不同的编码进行转换
    13. * @param code
    14. * @return
    15. */
    16. public String transform(String code){
    17. TransferService transferService = transferServiceMap.get(CodeEnum.of(code));
    18. Assert.notNull(transferService,"找不到对应的转换器");
    19. return transferService.transfer();
    20. }
    21. }
    22. 复制代码

    重构后是不是就很简洁了呢?如果后续新增新的编码转换器,只需要先在枚举里定义,然后新增实现类实现方法就行了,不需要对关心是怎么调用的,只关心具体的实现逻辑,降低了维护成本。

    这才是策略模式的真正应用吧。不要再乱用了,哈哈哈。

  • 相关阅读:
    深度学习如何具有人类智能实现论述假说
    大数据技术基础实验十二:YARN实验——部署YARN集群
    路由懒加载
    Let‘s Encrypt 使用
    Python正则表达式
    Apache Kylin新手小白入门教程
    【车间调度】基于遗传算法的柔性车间调度(Matlab代码实现)
    (22杭电多校二)Two Permutation (dp),Package Delivery (贪心)
    软考:中间件
    JavaIO系列——文件操作类(File)
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/128198261