被迫重构代码,这次我干掉了 if-else
本文收录在个人博客:www.chengxy-nds.top,技术资源共享,一起进步
最近公司貌似融到资了!开始发了疯似的找渠道推广,现在终于明白为啥前一段大肆的招人了,原来是在下一盘大棋,对员工总的来看是个好事,或许是时候该跟boss提一提涨工资的话题了。
不过,涨工资还没下文,随之而来的却是一车一车的需求,每天都有新渠道接入,而且每个渠道都要提供个性化支持,开发量陡增。最近都没什么时间更文,准点下班都成奢望了!
由于推广渠道的激增,而每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源,已经把之前的下单逻辑改的面目全。出于长远的考虑,我决定对现有的逻辑进行重构,毕竟长痛不如短痛。
传统的实现方式
我们看下边的伪代码,大致就是重构前下单逻辑的代码,由于来源比较少,简单的做if-else
逻辑判断足以满足需求。
现在每种订单来源的处理逻辑都有几百行代码,看着已经比较臃肿,可我愣是迟迟没动手重构,一方面业务方总像催命鬼一样的让你赶工期,想快速实现需求,这样写是最快;另一方面是不敢动,面对古董
级代码,还是想求个安稳。
但这次来源一下子增加几十个,再用这种方式做已经无法维护了,想象一下那种臃肿的if-else
代码,别说开发想想都头大!
public class OrderServiceImpl implements IOrderService {
@Override
public String handle(OrderDTO dto) {
String type = dto.getType();
if ("1".equals(type)) {
return "处理普通订单";
} else if ("2".equals(type)) {
return "处理团购订单";
} else if ("3".equals(type)) {
return "处理促销订单";
}
return null;
}
}
策略模式的实现方式
思来想去基于当前业务场景重构,还是用策略模式比较合适,它是oop
中比较著名的设计模式之一,对方法行为的抽象。
策略模式定义了一个拥有共同行为的算法族,每个算法都被封装起来,可以互相替换,独立于客户端而变化。
一、策略模式的使用场景:
- 针对同一问题的多种处理方式,仅仅是具体行为有差别时;
- 需要安全地封装多种同一类型的操作时;
- 同一抽象类有多个子类,而客户端需要使用
if-else
或者switch-case
来选择具体子类时。
这个是用策略模式修改后代码:
@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{
@Autowired
private OrderStencilledService orderStencilledService;
@Override
public void handle(OrderBO orderBO) {
/**
* 订单完结广播通知(1 - 支付完成)
*/
orderStencilledService.dispatchModeFanout(orderBO);
/**
* SCMS 出库单
*/
orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());
}
}
每个订单来源都有自己单独的逻辑实现类,而每次需要添加订单来源,直接新建实现类,修改@OrderHandlerType(16)
的数值即可,再也不用去翻又臭又长的if-lese
。
不仅如此在分配任务时,每个人负责开发几种订单来源逻辑,都可以做到互不干扰,而且很大程度上减少了合并代码的冲突。
二、具体的实现过程:
1、定义注解
定义一个标识订单来源的注解@OrderHandlerType
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
int value() default 0;
}
2、抽象业务处理器
向上抽象出来一个具体的业务处理器
public abstract class AbstractHandler {
abstract public void handle(OrderBO orderBO);
}
3、项目启动扫描 handler
入口
@Component
@SuppressWarnings({"unused","rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {
private String basePackage = "com.ecej.order.pipeline.processor";
public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Map<Integer,Class> map = new HashMap<Integer,Class>();
ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{
int type = x.getAnnotation(OrderHandlerType.class).value();
map.put(type,x);
});
beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);
log.info("处理器初始化{}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));
}
}
4、扫描需要用到的工具类
public class ClassScaner {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();
private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
/**
* 添加包含的Fiter
* @param includeFilter
*/
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
/**
* 添加排除的Fiter
* @param includeFilter
*/
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(excludeFilter);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackage 包名
* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解
* @return Set<Class<?>>
*/
public static Set<Class<?>> scan(String basePackage,
Class<?>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class<?> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
return cs.doScan(basePackage);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名,多个
* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解
* @return Set<Class<?>>
*/
public static Set<Class<?>> scan(String[] basePackages,
Class<?>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class<?> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String s : basePackages){
classes.addAll(cs.doScan(s));
}
return classes;
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String [] basePackages) {
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String basePackage :basePackages) {
classes.addAll(doScan(basePackage));
}
return classes;
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String basePackage) {
Set<Class<?>> classes = new HashSet<Class<?>>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(
SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (ClassNotFoundException ignore) {}
}
}
}
} catch (IOException ex) {
throw new RuntimeException("I/O failure during classpath scanning", ex);
}
return classes;
}
/**
* 处理 excludeFilters和includeFilters
* @param metadataReader
* @return boolean
* @throws IOException
*/
private boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}
5、根据类型实例化抽象类
@Component
public class HandlerContext {
@Autowired
private ApplicationContext beanFactory;
public AbstractHandler getInstance(Integer type){
Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());
return (AbstractHandler)beanFactory.getBean(map.get(type));
}
}
6、调用入口
我这里是在接受到MQ消息时,处理多个订单来源业务,不同订单来源路由到不同的业务处理类中。
@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{
private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);
@Autowired
private HandlerContext HandlerContext;
@Autowired
private OrderValidateService orderValidateService;
@RabbitHandler
public void subscribeMessage(MessageBean bean){
OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);
if(null != orderBO &&CollectionUtils.isNotEmpty(bean.getType()))
{
for(int value:bean.getType())
{
AbstractHandler handler = HandlerContext.getInstance(value);
handler.handle(orderBO);
}
}
}
}
接收实体 MessageBean
类代码
public class MessageBean implements Serializable {
private static final long serialVersionUID = 5454831432308782668L;
private String cachKey;
private List<Integer> type;
private String orderBO;
public MessageBean(List<Integer> type, String orderBO) {
this.type = type;
this.orderBO = orderBO;
}
}
以上设计模式方式看着略显复杂,很些小伙伴提出质疑:“你为了个if-else
,弄的如此的麻烦,又是自定义注解,又弄这么多类不麻烦吗?” 还有一些小伙伴纠结于性能问题,策略模式的性能可能确实不如if-else
。
但我觉得吧增加一点复杂度、牺牲一丢丢性能,换代码的整洁和可维护性还是值得的。不过,一个人一个想法,怎么选还是看具体业务场景吧!
策略模式的优缺点
优点
- 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
- 避免使用多重条件选择语句,充分体现面向对象设计思想 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
- 每个策略类使用一个策略类,符合单一职责原则 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
- 客户端不需要知道都有哪些策略类,符合最小知识原则
缺点
- 策略模式,当策略算法太多时,会造成很多的策略类
- 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过封装common公共包解决,也可以考虑使
IOC容器
和依赖注入
的方式来解决。
以下是订单来源策略类的一部分,不得不说策略类确实比较多。
总结
凡事都有他的两面性,if-else
多层嵌套和也都有其各自的优缺点:
if-else
的优点就是简单,想快速迭代功能,逻辑嵌套少且不会持续增加,if-else
更好些,缺点也是显而易见,代码臃肿繁琐不便于维护。策略模式
将各个场景的逻辑剥离出来维护,同一抽象类有多个子类,需要使用if-else
或者switch-case
来选择具体子类时,建议选策略模式,他的缺点就是会产生比较多的策略类文件。
两种实现方式各有利弊,如何选择还是要依据具体业务场景,还是那句话设计模式不是为了用而用,一定要用在最合适的位置。
闲聊
平常和粉丝私下聊天,好多人对于学设计模式的感受:设计模式背了一大堆,可平常开发还不是成天写if-else
业务逻辑,根本就用不到。
学设计模式也不是用不到,只是有时候没有合适它的场景而已,像我们今天说的这种业务场景,用设计模式就可以完美的解决嘛。
学了N多技术可工作用不到是一种很常见的事情,一个稳定的项目使用一种技术会有诸多考量的,新技术会不会提升系统复杂度?它有哪些性能瓶颈?这些都必须考虑到,毕竟项目稳定才是最重要,谁也不敢轻易冒险尝试。
而我们学习技术可不仅为了眼下项目中是否会用到,是要做一个技术积累,做长远打算,人往高处走,没点能力可不行。
原创不易,燃烧秀发输出内容,希望你能有一丢丢收获!
整理了几百本各类技术电子书,送给小伙伴们。关公众号回复【666】自行领取。和一些小伙伴们建了一个技术交流群,一起探讨技术、分享技术资料,旨在共同学习进步,如果感兴趣就扫码加入我们吧!
被迫重构代码,这次我干掉了 if-else的更多相关文章
- 一次项目代码重构-使用spring容器干掉条件判断
一次项目代码重构-使用spring容器干掉条件判断 这是在一次公司项目中进行重构时,一些复杂业务时想到的一个去掉一些if else的办法.能够使代码逻辑更加清晰,减少一些业务上的耦合. 业务说明 我所 ...
- 第四章:重构代码[学习Android Studio汉化教程]
第四章 Refactoring Code The solutions you develop in Android Studio will not always follow a straight p ...
- Winform打砖块游戏制作step by step第5节---重构代码,利用继承多态
一 引子 为了让更多的编程初学者,轻松愉快地掌握面向对象的思考方法,对象继承和多态的妙用,故推出此系列随笔,还望大家多多支持. 二 本节内容---重构代码,利用继承多态 1. 主界面截图如下: 2. ...
- eclipse 重构代码自动抽取函数
1.选择重构代码段 2.重构 – 抽取方法 3.命名重构代码段抽取的方法 4.使用抽取的方法
- 使用IDEA重构代码
使用IDEA提供的快捷操作,高效快速重构代码. 常用重构快捷菜单 Shift+F6,重构 - 重命名 Ctrl+Alt+m,提取方法 F6,移动方法
- 重构与反思-<重构代码的7个阶段>有感
https://coolshell.cn/articles/5201.html/comment-page-2#comment-1932554 过去半年基本上完整经历了这个文章的各个阶段,看完文章结合自 ...
- 探究重构代码(Code refactoring)
Code refactoring 是什么 在不改变软件的外部行为的条件下,通过修改代码改变软件内部结构,将效率低下和过于复杂的代码转换为更高效.更简单和更简单的代码. 怎样执行Code refacto ...
- 《Android Studio实战 快速、高效地构建Android应用》--三、重构代码
要成为高效的Android程序员,需要头脑灵活,能够在开发.调试和测试的过程中重构代码,重构代码最大的风险是可能会引入意外的错误,Android Studio通过分析某些具有危险性的重构操作来降低风险 ...
- Java8-19-lambda 重构代码
通过本书的前七章,我们了解了Lambda和Stream API的强大威力. 你可能主要在新项目的代码中使用这些特性.如果你创建的是全新的Java项目,这是极好的时机,你可以轻装上阵,迅速地将新特性应用 ...
随机推荐
- 「雕爷学编程」Arduino动手做(30)——光敏二极管模块
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(动手做)的理念,以学习和交流为目的,这里准备逐 ...
- 静态MAC地址配置案例
目录导航: 1.静态MAC地址简介 2.组网需求 3.配置思路 4.配置步骤 5.配置文件 1.静态MAC地址简介 返回目录导航 >MAC地址表项是交换机通过报文的源MAC地址学习过程而自动生成 ...
- 适配器模式C++实现
目录 类适配器 对象适配器 类适配器 #include <iostream> using namespace std; // Target class Target { public: v ...
- MYSQL LOCK IN SHARE MODE&FOR UPDATE
SELECT ... LOCK IN SHARE MODE sets a shared mode lock on the rows read. A shared mode lock enables o ...
- 玩转java反射
玩玩JAVA反射 什么是反射 Java反射机制是在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法:对于任何一个对象,都能够调用它的任何一个方法和属性:这样动态获取新的以及动态调用对象的方法 ...
- 十、理解JavaBean
1. 理解Bean 1.JavaBean本身就是一个类,属于Java的面向对象编程. 2.在JSP中如果要应用JSP提供的Javabean的标签来操作简单类的话,则此类必须满足如下的开发要求: (1) ...
- 【XSS-labs】Level 1-5
写在前面: 这个闯关游戏旨在理解XSS的原理并运用各种姿势绕过对于XSS攻击的过滤和限制. 这个练习只要弹出弹框即可过关 ,每一关我也会附上payload和源代码的解析 Level 1 观察源码 &l ...
- 剑指offer——数据结构
技术面重点:数组.字符串.链表.树.栈以及队列.
- 【Java】向*.txt文档里面重复添加同一个字符串
闺蜜说让我用代码写五万个对不起给她~~ import java.io.FileWriter; import java.io.IOException; /** * Created by lenovo o ...
- static关键字修饰属性
static 静态的,可以修饰属性,方法,代码块(或初始化块) , 内部内 非static修饰的属性(实例变量):各个对象各自拥有一套各自的副本 static修饰属性(l类变量): 1.由类创建的所有 ...