最近一直有小伙伴和同事询问楼主为什么最近没有更新博客啊,楼主无奈。主要是因为楼主最近一直在苦读的过程中,12月份完成了两本书的阅读,一本是Brain《Java并发编程实战》,另外一本则是12月29日完成的瑞·达利欧满怀真诚的著作**《原则》**一书,这本书也是之前我徒弟用获得的购书卡给我买的,这本书主要分为三个部分,一是瑞的经历,另外就是两部分瑞通过实践的原则:生活原则和工作原则,看了之后也是收获很多,自己也要在自己的生活之余创建属于自己的生活原则和工作原则,这样在遇到事情和问题时,才能有条不紊的去解决。在这里简单的摘要两段对我影响比较深的话,也算是给读者做一个推荐:
p170用五步流程实现你的人生愿望:
p410在桥水,我们秉持的主要共同价值观是从事有意义的工作,发展有意义的人际交往,做到极度求真和极度透明,愿意以开放的心态探索严酷的现实(包括正视自身缺点),有主人翁精神,敢于追求卓越,愿意做困难但有益的事情。
p317创意择优=极度求真+极度透明+可信度加权的决策。
喜欢阅读的人,别错过这本书。
闲言少叙,本文主要是阐述如何使用策略模式彻底去除if-else分支的跳转逻辑。因此楼主先提出一个场景出来,然后根据这个场景进行逐步的解决。
楼主在最近的工作中,有一个简单的需求,如下图所示:
模块B与模块A之间存在着许多的消息,而且消息的类型不同,数据内容也有所差异,早期的时候,消息类型较少,比如说只有三种消息,此时我们尚且可以通过if-else或者switch的方式应对,可随着项目的进展,分支越来越多,最终成为了如下的模样:
@Async
public 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曾经在第九章专门讨论过相关的内容:
换句话说,我们用策略模式,相当于就是在使用面向对象中的多态取代条件表达式的战略目的。
因此,其实解决本问题的过程,相当于就是我们要提炼出一个继承体系来了。
策略模式主要组成如下图所示:
在Java设计模式第p56页,辛格说:“行为模式的一个特定情况,是我们需要改变解决一个问题与另一个问题的方式。”但正如开闭原则所说,改变是不好的,而扩展是好的。因此,我们可以将两块代码封装在一个类中,而不是用一部分代码替换另一部分代码。然后可以创建代码所需要依赖累的抽象。
假设有这么一个需求:
一个电商系统,当用户消费满1000 金额,可以根据用户VIP等级,享受打折优惠。根据用户VIP等级,计算出用户最终的费用。
最简答的编码实现如下:
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 {
@Override
public double compute(long money) {
System.out.println("普通会员 不打折");
return money;
}
}
// 白银会员策略
public class SilverStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("白银会员 优惠50元");
return money - 50;
}
}
// 黄金会员策略
public class GoldStrategy implements Strategy{
@Override
public double compute(long money) {
System.out.println("黄金会员 8折");
return money * 0.8;
}
}
// 白金会员策略
public class PlatinumStrategy implements Strategy {
@Override
public 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().compute(money);
} else if (type == UserType.GOLD_VIP.getCode()) {
result = new GoldStrategy().compute(money);
} else if (type == UserType.PLATINUM_VIP.getCode()) {
result = new PlatinumStrategy().compute(money);
} else {
result = new OrdinaryStrategy().compute(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 strategy.compute(money);
}
上述代码使用了卫语句,这并不重要,我们关注一下if-else分支,可以看到,此时就把策略的获取和分支的计算给拆分开了。
上述if-else分支,可以继续提炼成函数,根据类型获取策略
private static double getResult(long money, int type) {
Strategy strategy = getStrategy(type);
return strategy.compute(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).compute(money);
}
但是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获取具体的策略模式,这是一种映射关系Mapping(Map就是映射数据结构结构),而映射关系的图示关系如下图所示:
由于我们在实际情况下,策略模式变化较少,而且数量固定,因此,我们可以直接生成所有的策略模式,这样在之后只需要直接调用这些策略模式即可。Map是直观的,接下来的代码采用了把类型信息放置在具体类中,更加符合面向对象嘛,然后使用List结构存储系统中存在的所有具体策略。
public interface Strategy {
double compute(long money);
// 返回 type变化,工冻加了类型返回
int getType();
}
public class OrdinaryStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("普通会员 不打折");
return money;
}
// 添加 type 返回
@Override
public int getType() {
return UserType.ORDINARY.getCode();
}
}
public class SilverStrategy implements Strategy {
@Override
public double compute(long money) {
System.out.println("白银会员 优惠50元");
return money - 50;
}
// type 返回
@Override
public 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 strategy.compute(money);
}
当然上述的函数过程,采用了对于getStrategy(type)直接内联掉了,因为只有行语句调用。
其实上述的过程已经完美的解决了if-else分支的问题,但由于我们使用了Spring Boot框架,基于这个框架,我们还可以再进一步优化去除策略工厂的手动创建过程。
我们对于消息处理器引入继承体系MessageHandler
上图中,并没有展示所有的子类体系,但这并不妨碍。首先MessageHandler是一个接口,而其子类在实现该接口的同时,同时为子类添加了注解**@Component**,这相当于省略了StrategyFactory类中的构造器的执行过程,因为IOC容器会自动为我们生成组件。
package com.cetc52.situation.service;
import com.alibaba.fastjson.JSONObject;
import com.cetc52.situation.common.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 {
@Autowired
private DeviceService deviceService;
@Autowired
private WebSocketService webSocketService;
@Override
public Message toMessage(JSONObject message) {
DeviceStatusEntity deviceMsg = getDeviceStatusEntity(message);
return Message.newDeviceStatusMessage(deviceMsg);
}
/**
* 处理设备状态报文消息
* 1. 判断设备状态是否发生变化。
* 2. 若变化则更新数据库中的设备状态信息,并使用websocket通知页面
*
* @param message
*/
@Override
public 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);
}
@Override
public 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(ZmqMessageType.DEVICE_STATE)
public class DeviceStateMessageHandler implements MessageHandler {...}
这是我们使用了一种简单的技巧,直接把ioc管理的这个Bean命名成其类型码了,这也是一种取巧的手段,并不华丽,但比较实用。
package com.cetc52.situation.service;
import com.alibaba.fastjson.JSONObject;
import com.cetc52.situation.common.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 {
@Autowired
private WebSocketService webSocketService;
@Override
public Message toMessage(JSONObject message) {
return Message.newCleanMessage(message.toJSONString());
}
@Override
public void handle(JSONObject message) {
sendMessage(toMessage(message));
}
@Override
public 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 {
@Autowired
private Map<String, MessageHandler> messageHandlers;
/**
* 消息处理
*
* @param message 消息体
*/
@Async
public 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);
}
}
@Autowired
private 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分支跳转
好了,亲爱的读者,或者说亲爱的程序员,这便是本文的全部内容了,程序员是一个并不是多好的工作,虽然薪水比较高,但还是希望读者能够爱惜自己的身体,坚持运动和早睡早起,有一个良好的身体。能够不断的朝着自己的目标前进前进前进。
最后,以笔者最近喜欢的一首李涉的诗歌来结束这个文章,爱好诗歌的人,也可以背诵下这首诗歌,万一可以卖弄呢?