SpringBoot使用策略模式彻底消除if

编程入门 行业动态 更新时间:2024-10-05 19:17:06

SpringBoot使用<a href=https://www.elefans.com/category/jswz/34/1771442.html style=策略模式彻底消除if"/>

SpringBoot使用策略模式彻底消除if

SpringBoot使用策略模式完成if-else分支跳转的优化

问题简介

 最近一直有小伙伴和同事询问楼主为什么最近没有更新博客啊,楼主无奈。主要是因为楼主最近一直在苦读的过程中,12月份完成了两本书的阅读,一本是Brain《Java并发编程实战》,另外一本则是12月29日完成的瑞·达利欧满怀真诚的著作**《原则》**一书,这本书也是之前我徒弟用获得的购书卡给我买的,这本书主要分为三个部分,一是瑞的经历,另外就是两部分瑞通过实践的原则:生活原则和工作原则,看了之后也是收获很多,自己也要在自己的生活之余创建属于自己的生活原则和工作原则,这样在遇到事情和问题时,才能有条不紊的去解决。在这里简单的摘要两段对我影响比较深的话,也算是给读者做一个推荐:

 p170用五步流程实现你的人生愿望:

  1. 有明确的目标
  2. 找到阻碍你实现这些目标的问题,并且不容忍问题、
  3. 准确诊断问题,找到问题的根源。
  4. 规划可以解决问题的方案
  5. 做一切必要的事来践行这些方案,实现成果。

p410在桥水,我们秉持的主要共同价值观是从事有意义的工作,发展有意义的人际交往,做到极度求真和极度透明,愿意以开放的心态探索严酷的现实(包括正视自身缺点),有主人翁精神,敢于追求卓越,愿意做困难但有益的事情。

p317创意择优=极度求真+极度透明+可信度加权的决策。

喜欢阅读的人,别错过这本书。

闲言少叙,本文主要是阐述如何使用策略模式彻底去除if-else分支的跳转逻辑。因此楼主先提出一个场景出来,然后根据这个场景进行逐步的解决。

 楼主在最近的工作中,有一个简单的需求,如下图所示:

 模块B与模块A之间存在着许多的消息,而且消息的类型不同,数据内容也有所差异,早期的时候,消息类型较少,比如说只有三种消息,此时我们尚且可以通过if-else或者switch的方式应对,可随着项目的进展,分支越来越多,最终成为了如下的模样:

    @Asyncpublic void dealFuseUploadData(byte[] data) {JSONObject fuseUploadInfo = JSONObject.parseObject(new String(data));final Integer messageType = fuseUploadInfo.getInteger("type");if(messageType == MessageType.END_TYPE_TEST){webSocketService.sendMsg(fuseUploadInfo);return;}JSONObject message = fuseUploadInfo.getJSONObject("data");switch (messageType) {case MessageType.FUSE_TARGET:handleFuseTarget(message);break;case MessageType.DEVICE_STATE:handleDeviceMessage(message);break;case MessageType.TRACK:handleTrackMessage(message);break;case MessageType.DEVICE_OPERATION_RESPONSE:// 处理设备响应信息webSocketService.sendMsg(new DeviceOperationResponseMessageTo(message));break;case MessageType.MESSAGE:// 处理设备实时报文信息log.info("设备实时报文:"+fuseUploadInfo);handleDeviceRealTimeMessage(message);break;case MessageType.MESSAGE_LENGTH:// 处理设备报文字节数log.info("设备报文字节数:"+fuseUploadInfo);deviceService.setDeviceMessageBytes(message.getString("deviceId"), message);break;default:break;}}

 可以看到,这样的代码让我很痛苦,函数名dealFuseUploadData也是起初的名字,当很明显,这个函数已经承担了过多的职责,它需要知道太多的跳转接口了,而这些转换其实并非应该由函数自己来做。由于这些函数比如说handleDeviceRealTimeMessage、handleFuseTarget的提取和不断的函数提炼,导致此时该类文件也将近400多行了,这都是敏感的信号,这个函数需要重构了,需要把过多的职责拆分出去。并且这个函数的扩展性不足,当前已经有了7种消息类型,未来有8种的时候,我们首先要添加case分支,然后要在MessageType类中添加消息类型嘛,然后还要添加对应的处理过程,这显然不是符合面向对象的一种设计,我们想要的是具有可扩展性的程序实现,比如说,新添加一种类型,我们希望跳转的类不需要做任何改动,只需要添加相应的类型处理器来实现有关的处理即可。那这样的设想应该怎么实现呢?

使用多态替换条件表达式

 我们可以看到,上述的问题其实最主要的还是聚焦于条件表达式的处理,在《重构 改善既有代码的设计中》,Martin Folwer曾经在第九章专门讨论过相关的内容:

 换句话说,我们用策略模式,相当于就是在使用面向对象中的多态取代条件表达式的战略目的。

 因此,其实解决本问题的过程,相当于就是我们要提炼出一个继承体系来了。

策略模式描述

 策略模式主要组成如下图所示:

  • Strategy Strategy角色负责决定实现策略所必需的接口(API)。在示例程序中,由strategy接口扮演此角色。 特定策略的抽象。
  • ConcreteStrategy(具体策略)ConcreteStrategy角色负责实现Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。
  • Context(上下文,运行特定策略的类。)负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口(API))。可以认为由Context处理了跳转的功能。
  • 策略模式的目的:策略模式定义了一系列算法,封装了每个算法,并使它们可以互换。

在Java设计模式第p56页,辛格说:“行为模式的一个特定情况,是我们需要改变解决一个问题与另一个问题的方式。”但正如开闭原则所说,改变是不好的,而扩展是好的。因此,我们可以将两块代码封装在一个类中,而不是用一部分代码替换另一部分代码。然后可以创建代码所需要依赖累的抽象。

问题

假设有这么一个需求:

一个电商系统,当用户消费满1000 金额,可以根据用户VIP等级,享受打折优惠。根据用户VIP等级,计算出用户最终的费用。

  • 普通会员 不打折
  • 白银会员 优惠50元
  • 黄金会员 8折
  • 白金会员 优惠50元,再打7折

最简答的编码实现如下:

private static double getResult(long money, int type) {double result = money;if (money >= 1000) {if (type == UserType.SILVER_VIP.getCode()) {System.out.println("白银会员 优惠50元");result = money - 50;} else if (type == UserType.GOLD_VIP.getCode()) {System.out.println("黄金会员 8折");result = money * 0.8;} else if (type == UserType.PLATINUM_VIP.getCode()) {System.out.println("白金会员 优惠50元,再打7折");result = (money - 50) * 0.7;} else {System.out.println("普通会员 不打折");result = money;}}return result;
}

重构启程-引入策略继承体系

这是我们功能的基础,我们从这个进一步进行优化,但不论怎么说,这是第一版,功能已经实现,只是代码的质量需要提升。

public interface Strategy {// 计费方法double compute(long money);
}// 普通会员策略
public class OrdinaryStrategy implements Strategy {@Overridepublic double compute(long money) {System.out.println("普通会员 不打折");return money;}
}// 白银会员策略
public class SilverStrategy implements Strategy {@Overridepublic double compute(long money) {System.out.println("白银会员 优惠50元");return money - 50;}
}// 黄金会员策略
public class GoldStrategy implements Strategy{@Overridepublic double compute(long money) {System.out.println("黄金会员 8折");return money * 0.8;}
}// 白金会员策略
public class PlatinumStrategy implements Strategy {@Overridepublic double compute(long money) {System.out.println("白金会员 优惠50元,再打7折");return (money - 50) * 0.7;}
}

上述从if-else到Strategy继承体系的过程,是通过考察if-else分支中的主要过程,通过money获得result的过程,提炼成了Strategy抽象策略类。

使用具体策略类

private static double getResult(long money, int type) {double result = money;if (money >= 1000) {if (type == UserType.SILVER_VIP.getCode()) {result = new SilverStrategy()pute(money);} else if (type == UserType.GOLD_VIP.getCode()) {result = new GoldStrategy()pute(money);} else if (type == UserType.PLATINUM_VIP.getCode()) {result = new PlatinumStrategy()pute(money);} else {result = new OrdinaryStrategy()pute(money);}}return result;
}

此处很关键,要比较敏感,if-else都做了什么。善于发现美的眼睛很重要。

发现重复-程序员智慧的双眼

拆分获取策略和计算的过程

首先,这个过程还是不符合只做一件事。把获取计算策略和计算的过程耦合在一起了,因此,我们进行再一步的分离。把获取策略和计算分离,使得函数更加符合单一职责

private static double getResult(long money, int type) {if (money < 1000) {return money;}Strategy strategy;if (type == UserType.SILVER_VIP.getCode()) {strategy = new SilverStrategy();} else if (type == UserType.GOLD_VIP.getCode()) {strategy = new GoldStrategy();} else if (type == UserType.PLATINUM_VIP.getCode()) {strategy = new PlatinumStrategy();} else {strategy = new OrdinaryStrategy();}return strategypute(money);
}

上述代码使用了卫语句,这并不重要,我们关注一下if-else分支,可以看到,此时就把策略的获取和分支的计算给拆分开了。

上述if-else分支,可以继续提炼成函数,根据类型获取策略

private static double getResult(long money, int type) {Strategy strategy = getStrategy(type);return strategypute(money);
}private Strategy getStrategy(UserType type) {Strategy strategy;if (type == UserType.SILVER_VIP.getCode()) {strategy = new SilverStrategy();} else if (type == UserType.GOLD_VIP.getCode()) {strategy = new GoldStrategy();} else if (type == UserType.PLATINUM_VIP.getCode()) {strategy = new PlatinumStrategy();} else {strategy = new OrdinaryStrategy();}return stratety;
}

上述代码就相对来说比较简单来了,我们甚至可以在getResult函数中使用内联方式,因为代码足够简单嘛,获取了strategy,然后直接调用了,没有必要独占一个变量名,没有意义(我是Martin Folwer的忠实粉丝

private static double getResult(long money, int type) {return getStrategy(type)pute(money);
}

终极移除if-else

 但是if-else仍然存在,接下来就是关键的进一步优化了,使用了工厂模式和策略模式整合在一起。请认真体会。

private Strategy getStrategy(UserType type) {Strategy strategy;if (type == UserType.SILVER_VIP.getCode()) {strategy = new SilverStrategy();} else if (type == UserType.GOLD_VIP.getCode()) {strategy = new GoldStrategy();} else if (type == UserType.PLATINUM_VIP.getCode()) {strategy = new PlatinumStrategy();} else {strategy = new OrdinaryStrategy();}return stratety;
}

 作为程序员,走到了这一步,其实我们可以再仔细观察,上述的if-else的主要结构主要是通过type获取具体的策略模式,这是一种映射关系MappingMap就是映射数据结构结构),而映射关系的图示关系如下图所示:

由于我们在实际情况下,策略模式变化较少,而且数量固定,因此,我们可以直接生成所有的策略模式,这样在之后只需要直接调用这些策略模式即可。Map是直观的,接下来的代码采用了把类型信息放置在具体类中,更加符合面向对象嘛,然后使用List结构存储系统中存在的所有具体策略

public interface Strategy {double compute(long money);// 返回 type变化,工冻加了类型返回int getType();
}public class OrdinaryStrategy implements Strategy {@Overridepublic double compute(long money) {System.out.println("普通会员 不打折");return money;}// 添加 type 返回@Overridepublic int getType() {return UserType.ORDINARY.getCode();}
}public class SilverStrategy implements Strategy {@Overridepublic double compute(long money) {System.out.println("白银会员 优惠50元");return money - 50;}// type 返回@Overridepublic int getType() {return UserType.SILVER_VIP.getCode();}
}....省略剩下 Strategy

此时,我们可以做的是把所有的策略,因此提炼出策略工厂类,如下:

public class StrategyFactory {private Map<Integer, Strategy> map;/*构造器,其实实现了从List到Map的转换*/public StrategyFactory() {List<Strategy> strategies = new ArrayList<>();strategies.add(new OrdinaryStrategy());strategies.add(new SilverStrategy());strategies.add(new GoldStrategy());strategies.add(new PlatinumStrategy());strategies.add(new PlatinumStrategy());// 看这里 看这里 看这里!哈哈哈,好的好的。map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));/* 等同上面map = new HashMap<>();for (Strategy strategy : strategies) {map.put(strategy.getType(), strategy);}*/}public static class Holder {public static StrategyFactory instance = new StrategyFactory();}public static StrategyFactory getInstance() {return Holder.instance;}public Strategy get(Integer type) {return map.get(type);}
}

因此,至此getStrategy函数则可以进一步重构:

private Strategy getStrategy(UserType type) {Strategy strategy;if (type == UserType.SILVER_VIP.getCode()) {strategy = new SilverStrategy();} else if (type == UserType.GOLD_VIP.getCode()) {strategy = new GoldStrategy();} else if (type == UserType.PLATINUM_VIP.getCode()) {strategy = new PlatinumStrategy();} else {strategy = new OrdinaryStrategy();}return stratety;
}

我们把根据类型获取具体策略的的过程委托给策略工厂类StrategyFactory,则getStrategy函数重构为:

private Strategy getStrategy(UserType type) {return StrategyFactory.getInstance().get(type);
}

至此,我们便使用了策略模式完成了对于代码的重构,可以看到已经没有了if-else分支。

最终效果如下:

private static double getResult(long money, int type) {if (money < 1000) {return money;}Strategy strategy = StrategyFactory.getInstance().get(type);if (strategy == null){throw new IllegalArgumentException("please input right type");}return strategypute(money);
}

当然上述的函数过程,采用了对于getStrategy(type)直接内联掉了,因为只有行语句调用。

问题解答

 其实上述的过程已经完美的解决了if-else分支的问题,但由于我们使用了Spring Boot框架,基于这个框架,我们还可以再进一步优化去除策略工厂的手动创建过程

引入继承体系

我们对于消息处理器引入继承体系MessageHandler

 上图中,并没有展示所有的子类体系,但这并不妨碍。首先MessageHandler是一个接口,而其子类在实现该接口的同时,同时为子类添加了注解**@Component**,这相当于省略了StrategyFactory类中的构造器的执行过程,因为IOC容器会自动为我们生成组件。

引入@Componet实现具体策略

package com.cetc52.situation.service;import com.alibaba.fastjson.JSONObject;
import com.cetc52.situationmon.ZmqMessageType;
import com.cetc52.situation.domain.dto.Message;
import com.cetc52.situation.domain.entity.DeviceEntity;
import com.cetc52.situation.domain.entity.DeviceStatusEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Optional;/*** 设备状态消息处理器** @author songquanheng* 2021/12/2 16:08*/
@Component(ZmqMessageType.DEVICE_STATE)
public class DeviceStateMessageHandler implements MessageHandler {@Autowiredprivate DeviceService deviceService;@Autowiredprivate WebSocketService webSocketService;@Overridepublic Message toMessage(JSONObject message) {DeviceStatusEntity deviceMsg = getDeviceStatusEntity(message);return Message.newDeviceStatusMessage(deviceMsg);}/*** 处理设备状态报文消息* 1. 判断设备状态是否发生变化。* 2. 若变化则更新数据库中的设备状态信息,并使用websocket通知页面** @param message*/@Overridepublic void handle(JSONObject message) {DeviceStatusEntity deviceMsg = getDeviceStatusEntity(message);Optional<DeviceEntity> deviceEntityOptional = getDeviceEntity(deviceMsg);if (!deviceEntityOptional.isPresent()) {return;}sendMessage(toMessage(message));DeviceEntity deviceEntity = deviceEntityOptional.get();updateDeviceStatus(deviceMsg, deviceEntity);}/*** 根据设备状态报文信息更新数据库中设备实体** @param deviceMsg    最新上报的设备状态报文信息* @param deviceEntity 数据库中最新的设备实体*/public void updateDeviceStatus(DeviceStatusEntity deviceMsg, DeviceEntity deviceEntity) {deviceEntity.setDeviceStatus(deviceMsg.getDeviceStatus());deviceService.updateDeviceStatusAndUpdateTime(deviceEntity);}@Overridepublic void sendMessage(Message message) {webSocketService.sendMsg(message);}/*** 根据上传的设备状态信息获取设备状态实体类** @param message 融合上报的设备状态消息* @return*/private DeviceStatusEntity getDeviceStatusEntity(JSONObject message) {return message.toJavaObject(DeviceStatusEntity.class);}/*** 根据设备状态消息获取消息对应的设备实体** @param deviceMsg 设备状态消息* @return*/private Optional<DeviceEntity> getDeviceEntity(DeviceStatusEntity deviceMsg) {DeviceEntity deviceEntity = deviceService.findByDeviceId(deviceMsg.getDeviceId());return Optional.ofNullable(deviceEntity);}}

笔者要为这个类进行如下的阐述:

  • 因为使用了@Component注解,因此该类的实例会自动生成
  • 因为IOC中会有该Bean,因此,我们可以使用**@Autowired**注入由ioc管理的其他Bean
  • @Component(ZmqMessageType.DEVICE_STATE)
@Component(ZmqMessageType.DEVICE_STATE)
public class DeviceStateMessageHandler implements MessageHandler {...}

 这是我们使用了一种简单的技巧,直接把ioc管理的这个Bean命名成其类型码了,这也是一种取巧的手段,并不华丽,但比较实用。

另一个具体策略类

package com.cetc52.situation.service;import com.alibaba.fastjson.JSONObject;
import com.cetc52.situationmon.ZmqMessageType;
import com.cetc52.situation.domain.dto.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** 一轮目标数据发送完成消息处理器** @author songquanheng* 2021/12/2 18:27*/
@Component(ZmqMessageType.END_ROUND_TRANSMIT)
public class EndRoundMessageHandler implements MessageHandler {@Autowiredprivate WebSocketService webSocketService;@Overridepublic Message toMessage(JSONObject message) {return Message.newCleanMessage(message.toJSONString());}@Overridepublic void handle(JSONObject message) {sendMessage(toMessage(message));}@Overridepublic void sendMessage(Message message) {webSocketService.sendMsg(message);}
}

其他具体策略类,不再赘述。

工厂类

 在这里,再次利用Spring Boot的自动注入,这个则是自动注入Map的一种巧妙的用法。

package com.cetc52.situation.service;import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.util.Map;/*** 数据融合上报数据处理类** @author liquanming* 2021/9/16 19:52*/
@Service
@Slf4j
public class MessageService {@Autowiredprivate Map<String, MessageHandler> messageHandlers;/*** 消息处理** @param message 消息体*/@Asyncpublic void handelMessage(JSONObject message) {final MessageHandler handler = getMessageHandler(message);handler.handle(message.getJSONObject("data"));}/*** 获取消息对应的处理器** @param message 消息* @return 处理器*/private MessageHandler getMessageHandler(JSONObject message) {final String messageType = message.getString("type");return messageHandlers.get(messageType);}}
    @Autowiredprivate Map<String, MessageHandler> messageHandlers;

 在此,massageHandlers会由ioc自动注入,这样,我们并不需要显示的执行构造messageHandlers的过程,而系统会自动为我们把策略结合创建完成。另外的一个好处,就是,当约定了新的消息类型,连MessageService类型,我们并不需要做任何修改,只需要新创建一个具体策略类型即可优雅的完成工作,类的可扩展性拉满。这非常的优雅,而优雅就是程序员的最美好的品格

总结

 本篇文章意在通过使用工厂模式结合策略模式彻底取代if-else分支的出现。本文相较于之前的方法,如果要采用这样的方法,有几个点需要理解。

  • 策略继承体系的提炼,一般if-else两者是要对等的,如果不对等的话,请采用卫语句解决,对等的含义是代码相当,作用相当,类似门当户对。

  • @Component实现接口,并注入其他ioc所管理的bean,这是创造性的一步,因为可以直接把策略的类型由ioc生成和管理,这样也就省略了显式创造的过程,策略工厂虽然存在,但其实也就被MessageService类通过

    @autowired 
    private Map<String, MessageHandler> messageHandlers;
    

    给一键实现了。

  • 优秀的程序员发现重复,不容忍重复。因为重复是万恶之源。(其实就是瑞·达里欧所说的发现问题,不容忍问题)

    下载

    为防止使用图床失效,附上原文下载地址:

    使用策略模式消除if-else分支跳转

    好了,亲爱的读者,或者说亲爱的程序员,这便是本文的全部内容了,程序员是一个并不是多好的工作,虽然薪水比较高,但还是希望读者能够爱惜自己的身体,坚持运动和早睡早起,有一个良好的身体。能够不断的朝着自己的目标前进前进前进。

    最后,以笔者最近喜欢的一首李涉的诗歌来结束这个文章,爱好诗歌的人,也可以背诵下这首诗歌,万一可以卖弄呢?

    《题鹤林寺壁》
    终日昏昏醉梦间,忽闻春尽强登山;
    因过竹院逢僧话,偷得浮生半日闲。

更多推荐

SpringBoot使用策略模式彻底消除if

本文发布于:2024-02-28 10:56:38,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1769264.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:策略   模式   SpringBoot

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!