查看原文
其他

一次项目代码重构:使用Spring容器干掉条件判断

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约


每日英文

Don't make decisions when angry, don't say the promise at happy time.

请记住,别在生气的时候做决定,别在开心的时候说承诺。


每日掏心

所谓心事,不过是不如己意,那就是我执著于自己描画的理想,一有落差,即生烦恼。

来自:何白白 | 责编:乐乐

链接:cnblogs.com/hebaibai/p/11095590.html#4290447

程序员小乐(ID:study_tech)第 831 次推文   图片来自百度


往日回顾:为什么Spring MVC能自动转换json/xml,你研究过它背后的原理吗?


     

   正文   


这是在一次公司项目中进行重构时,一些复杂业务时想到的一个去掉一些if else的办法。能够使代码逻辑更加清晰,减少一些业务上的耦合。


业务说明


我所在的是一个做保险的项目组,这次重构是针对其中的保费计算和核保的业务。


项目重构之前,在保费计算的接口中,有大量的条件判断语句来判断这次进行保费计算的产品是哪一个,然后调用该产品的保费计算方法。代码大致看起来就是这个样子:


//产品编号
String product = "123123";
if (product.equals("11111")) {
} else if (product.equals("11111")) {
//使用产品编号是11111的service类进行保费计算
//
} else if (product.equals("22222")) {
//使用产品编号是22222的service类进行保费计算
//
} else {
//执行其他的保费计算
}


这些通过一个编号进行判断并执行特定代码的方法在项目中到处都是,一旦添加了新的产品,或者产品在一些特定销售渠道中有特定的一些操作(比如有一些折扣啊什么的)就要在代码的各种关键地方添加一堆 if eles,大大影响了代码的可读性和可维护性。常常是修改了一处代码,影响到很多别的地方,造成一些看起来很奇怪的bug。


所以我在接手这个项目的时候想到了重构。希望在重构后能够使各个产品之间的代码没有关联,业务分明,相互不影响。


开始重构

分析业务,抽象出接口


重构代码之前要先分析一下业务,因为我的这个项目是做保费计算的,虽然有一大堆的判断产品编号的代码,但是最终它们做的都是同一件事情。这里可以把保费计算抽象成为一个接口,在接口中定义执行保费计算所需要的共同的方法。大致上是这个样子:


/**
* 保费计算接口
*
* @author hjx
*/

public interface PremiumCalculate {

//保费计算
Result calculate(Param param);

//其他的保费计算中需要的方法
。。。
}


这一步主要是梳理一下项目中的业务,并把业务中相同的步骤抽象出来。

根据不同的产品实现不同产品的保费计算

这一步就是实现上面的接口了,每个产品实现一遍接口。这里会存在一个问题,就是有很多产品在进行保费计算的时候,只有某几个步骤不一样。如果每个实现上都写一遍,会造成大量的重复代码。


我们可以建一个抽象类来实现这个接口,将大部分共有的实现代码写在抽象类中。就像这样:


/**
* 抽象的保费计算
*/

public abstract class AbstractPremiumCalculate implements PremiumCalculate {
/**
* 实现共有的一些代码
* @param premiumInfo
* @return
*/

@Override
public Result calculate(Param param) {
//将共有的实现写在这里
}
}

这样在新添加一个实现类的时候只要继承这个抽象类,重写其中的某些方法就可以了。这时我们可能有很多实现类,比如:

  • PremiumCalculateP001Impl.java

  • PremiumCalculateP002Impl.java

  • PremiumCalculateP003Impl.java

  • PremiumCalculateP004Impl.java

    这时就可以在执行保费计算的时候根据不同的产品调用不同的实现。每个实现类只写一个产品的业务就行,类之间相互不影响。在新添加产品的时候也是只需要添加一个新的实现类就好了。

但是这样还有问题

但是这样还是有问题的,因为还是要在业务代码中写一堆的if else 来判断这次到底需要哪一个实现类来执行保费计算,这时可以写一个工厂类。根据传入的产品编号或者别的什么参数,返回特定的一个实现类,像是这样:


/**
* 保费计算接口工厂类
*/

@Service
public class PremiumCalculateFactory {

@Autowired
@Qualifier("premiumCalculateP0001")
private PremiumCalculate premiumCalculateP0001;

@Autowired
@Qualifier("premiumCalculateP0002")
private PremiumCalculate premiumCalculateP0002;

@Autowired
@Qualifier("premiumCalculateP0003")
private PremiumCalculate premiumCalculateP0003;

@Autowired
@Qualifier("premiumCalculateP0004")
private PremiumCalculate premiumCalculateP0004;

。。。。其他的保费计算实现对象

public PremiumCalculate getPremiumCalculate(String productCode) {

if (productCode.equals("P0001")) {
return premiumCalculateP0001;
} else if () {
//其他的条件下,返回其他的对象
}
}

}


添加了工厂类之后,我们在获取保费计算对象的时候只需要调用getPremiumCalculate()方法就可以了,具体返回哪一个实现对象,就交给工厂类来处理。可以精简调用保费计算时的代码。

还是免不了写if else,改造PremiumCalculateFactory

在提供了工厂类之后,还是免不了写很多的条件判断,只不过是把所有的条件判断写在了一起。这时随着产品数量的增多,if else 也会不停地增多,维护起来依然费劲。


这里spring容器就可以排上用场了。spring中有一个BeanFactory对象,也是一个工厂,我们可以用它来改造PremiumCalculateFactory。建议看下咱们这篇文章:为什么Spring MVC能自动转换json/xml,你研究过它背后的原理吗?


首先我们在编写各个产品对应的保费计算实现类的时候都会将它注册进spring容器中,成为一个bean。我们需要给这个bean指定一个名称比如:


//在这里指定bean的名称
@Service("PremiumCalculate:"+产品编号)
public class PremiumCalculateP0001Impl implements PremiumCalculate {
。。。
}


然后修改PremiumCalculateFactory


/**
* 保费计算接口工厂类
*/

@Service
public class PremiumCalculateFactory {
@Autowired
private BeanFactory beanFactory;

public PremiumCalculate getPremiumCalculate(String productCode) {
Object bean = beanFactory.getBean("PremiumCalculate:" + productCode);
if (bean instanceof PremiumCalculate) {
return (PremiumCalculate) bean;
}
throw new UnsupportedOperationException("不支持的编号:" + productCode);
}
}


关于Spring:【整理】Spring 常用注解!千万不要错过!

这样,条件判断的步骤就可以省略了。

结果

在保费计算和核保项目经过这样重构后,每个产品的业务代码相互不关联,维护和添加产品时也能减少工作量,还是比较成功的。

不足

这样写会有一个比较大的问题,就是在产品数量增多的时候,java文件数量也会随之变多。但是目前的业务中产品数量还可以忍受。由于产品配置功能的出现,大部分产品都可以通过数据库配置出来。这里只是写配不出来的一部分,所以这种模式还是可行的。

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,学习能力的提升上有新的认识,欢迎转发分享给更多人。

欢迎各位读者加入订阅号程序员小乐技术群,在后台回复“加群”或者“学习”即可。

猜你还想看


阿里、腾讯、百度、华为、京东最新面试题汇集

手把手教你 Netty 实现自定义协议!

一个简单的例子带你理解HashMap

JDK 中定时器是如何实现的

关注订阅号「程序员小乐」,收看更多精彩内容
嘿,你在看吗?
: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存