一次項目代碼重構-使用spring容器干掉條件判斷
這是在一次公司項目中進行重構時,一些復雜業務時想到的一個去掉一些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容器中,成為一個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);
}
}
這樣,條件判斷的步驟就可以省略了。
結果
在保費計算和核保項目經過這樣重構后,每個產品的業務代碼相互不關聯,維護和添加產品時也能減少工作量,還是比較成功的。
不足
這樣寫會有一個比較大的問題,就是在產品數量增多的時候,java文件數量也會隨之變多。但是目前的業務中產品數量還可以忍受。由於產品配置功能的出現,大部分產品都可以通過數據庫配置出來。這里只是寫配不出來的一部分,所以這種模式還是可行的。