如何優雅地優化代碼中的if else和switch case

如何優雅地優化代碼中的if else和switch case

引言

一般來說,隨著我們項目的迭代以及業務的越來越複雜,項目中的分支判斷會原來越多。當項目中涉及到複雜的業務判斷或者分支邏輯時,我們就需要考慮是否需要對項目進行重構了,或者if else和switch case是否能夠滿足當前項目的複雜度。

我們舉一個簡單的例子,假如我們是馬戲團的老闆,在訓練一些動物去做一些指令,剛開始很簡單,只訓練了一條狗,當狗握了一下手後,給她獎勵一些狗糧。這樣慢慢地小狗就學會了握手。

我們先定義一條小狗對象,小狗做了某些事情(“握手”)後,可以得到一些獎勵

public class Dog {
\tpublic void train(){
\t\tSystem.out.println("握手");
\t}
\tpublic void getReward(){
\t\tSystem.out.println("狗糧");
\t}
}

定義馴獸師,通過train來訓練動物

public class Beast {
\t/**
\t * 訓練
\t */
\tpublic void train(Dog dog){
\t\t/**

\t\t * 狗狗做了一些事情
\t\t */
\t\tdog.train();
\t\t/**
\t\t * 狗狗得到獎勵
\t\t */
\t\tdog.getReward();
\t}
}

如果我們只需要訓練一條動物,那麼相對來說比較簡單。但是後來馬戲團又引進了一頭獅子,需要訓練獅子鑽火圈,因此,為了區分狗和獅子,我們增加了一種類型區分訓練的動物是狗還是獅子。

public class Lion {
\tpublic void doSomething(){
\t\tSystem.out.println("鑽火圈");
\t}
\tpublic void getReward(){
\t\tSystem.out.println("得到一隻雞");
\t}
}

重新修改Beast類,使其既可以訓練小狗,又可以訓練獅子

public class Beast {

\tpublic void train(Object animal, int type){
\t\tif (type == 1){
\t\t\ttrainDog((Dog)animal);
\t\t}else if (type == 2){
\t\t\ttrainLion((Lion)animal);
\t\t}
\t}

\t/**
\t * 訓練
\t */
\tpublic void trainDog(Dog dog){

\t\t/**
\t\t * 狗狗做了一些事情
\t\t */
\t\tdog.doSomething();
\t\t/**
\t\t * 狗狗得到獎勵
\t\t */
\t\tdog.getReward();
\t}
\t/**
\t * 訓練
\t */
\tpublic void trainLion(Lion lion){
\t\t/**
\t\t * 狗狗做了一些事情
\t\t */
\t\tlion.doSomething();
\t\t/**
\t\t * 狗狗得到獎勵
\t\t */
\t\tlion.getReward();
\t}
}

我們通過type類型來區分訓練的動物類型,後來馬戲團引來了越來越多的動物,那我們的type的取值會越來越多:1表示狗,2表示獅子,3表示貓,4表示老虎,5表示猴子...等等。而且隨著系統越來越複雜,我們在訓練之前不同的動物還需要做不同的準備工作。當然,目前的系統只要增加if else或者switch case是可以滿足需求的,但這樣寫顯得不是太優雅,我們希望找到一種比較優雅比較有設計感的方式來取代if else或者switch case

優化if else

在做優化之前,我們需要先弄清楚我們的目的。我們是馬戲團的馴獸師,目的是訓練動物。

- 目的:訓練動物做一些事情(train)

- 方式:通過獎勵(getReward)誘導動物進行訓練。

有了上面的目的之後,我們就可以定義一個模型animal

public interface IAnimal {

\t/**
\t * 獲取動物種類
\t * @return
\t */
\tint getType();

\t/**
\t * 訓練動作
\t */
\tvoid train();
}

Animal只暴露兩個方法,其中doSomething是專門用來訓練動物的,至於如何訓練全都由子類實現。

- getType():用來區分不同的動物

- train():訓練動物

然後我們定義一個子類實現這個接口,用來具體化如何訓練動物

public abstract class AbsTrainAnimal implements IAnimal{

\t/**
\t * 訓練前需要做的準備
\t */
\tabstract void beforeTrain();

\t/**
\t * 訓練後需要做的準備
\t */
\tabstract void afterTrain();

\t/**
\t * 訓練出現異常需要做的
\t */
\tabstract void exceptionTrain(Throwable throwable);

\t/**
\t * 具體訓練
\t */
\tabstract void doSomething();

\t/**
\t * 訓練動作
\t */
\t@Override
\tpublic final void train() {
\t\ttry {
\t\t\tbeforeTrain();
\t\t\tdoSomething();
\t\t\tafterTrain();
\t\t}catch (Throwable throwable){
\t\t\texceptionTrain(throwable);
\t\t}
\t}
}

我們定義了一個抽象類用來實現IAnimal接口,作為所有動物訓練的基類。其中實現的接口train使用了final進行了限制,防止子類對其進行覆蓋操作。

在AbsTrainAnimal中,我們對train()進行了各種功能的細化

- doSomething:具體訓練的內容

- beforeTrain:訓練之前需要做的一些準備

- afterTrain:訓練之後需要做的事情

- exceptionTrain:訓練中發生意外應該如何處理

因為所有動物的以上四個方法可能都不相同,所以我們聲明為abstract方法,方便子類自己實現。基於以上設計,我們就可以定義一個Dog類,對其進行訓練。

public class Dog extends AbsTrainAnimal {
\t/**
\t * 訓練前需要做的準備
\t */
\t@Override
\tvoid beforeTrain() {
\t\tSystem.out.println("撫摸額頭以示鼓勵");
\t}

\t/**
\t * 訓練後需要做的準備
\t */
\t@Override
\tvoid afterTrain() {
\t\tSystem.out.println("獎勵一些狗糧");
\t}

\t/**
\t * 訓練出現異常需要做的
\t *
\t * @param throwable
\t */
\t@Override
\tvoid exceptionTrain(Throwable throwable) {
\t\tSystem.out.println("出去罰站");
\t}

\t/**
\t * 具體訓練
\t */
\t@Override

\tvoid doSomething() {
\t\tSystem.out.println("握手");
\t}

\t/**
\t * 獲取動物種類
\t *
\t * @return
\t */
\t@Override
\tpublic int getType() {
\t\treturn 1;
\t}
}

我們再定義一個Lion

public class Lion extends AbsTrainAnimal {
\t/**
\t * 訓練前需要做的準備
\t */
\t@Override
\tvoid beforeTrain() {
\t\tSystem.out.println("友好交流");
\t}

\t/**
\t * 訓練後需要做的準備
\t */
\t@Override
\tvoid afterTrain() {
\t\tSystem.out.println("獎勵一隻雞");
\t}

\t/**
\t * 訓練出現異常需要做的
\t *
\t * @param throwable
\t */
\t@Override
\tvoid exceptionTrain(Throwable throwable) {
\t\tSystem.out.println("緊急送往醫院");
\t}

\t/**

\t * 具體訓練
\t */
\t@Override
\tvoid doSomething() {
\t\tSystem.out.println("鑽火圈");
\t}

\t/**
\t * 獲取動物種類
\t *
\t * @return
\t */
\t@Override
\tpublic int getType() {
\t\treturn 2;
\t}
}

我們可以看到,Dog和Lion的動物種類是不一樣的,Dog為1,Lion為2。我們可以根據type區分是獅子還是狗。但為了避免使用if else進行區分,我們需要一個工廠類來生產這兩種動物。

@Service
public class AnimalFactory {

\tprivate static List> animalLists = Lists.newArrayList();
\tprivate static Map animalMaps = Maps.newHashMap();

\tstatic {
\t\tanimalLists.add(Dog.class);
\t\tanimalLists.add(Lion.class);
\t}

\t@PostConstruct
\tpublic void init() throws IllegalAccessException, InstantiationException {
\t\tfor (Class clazz : animalLists){
\t\t\tObject obj = clazz.newInstance();
\t\t\tanimalMaps.put(obj.getType(), obj);
\t\t}
\t}

\t/**
\t * 構建動物類
\t * @param type

\t * @return
\t */
\tIAnimal build(int type){
\t\treturn animalMaps.get(type);
\t}
}

我們有了這個工廠類,就可以根據不同的動物類型獲取不同的對象,並對其進行訓練。當然我們這裡都是使用的單例模式,每個對象只對應一個實例,如果每次都生成不同的實例,可以對其進行簡單的改造即可實現。

我們再重寫馴獸師Beast類

@Service
public class Beast {

\t@Resource
\tprivate AnimalFactory animalFactory;

\t/**
\t * 訓練動物,只需要知道動物的類型即可
\t * @param type
\t */
\tpublic void train(int type){
\t\tIAnimal animal = animalFactory.build(type);
\t\tanimal.train();
\t}
}

可以看到train方法只需要關係動物類型即可,不需要再根據type進行判斷動物類型在對其進行不同的操作。如果有新的動物加入,只需要實現AbsTrainAnimal基類,然後向AnimalFactory進行註冊即可。避免了根據不同type進行if else或者switch的判斷

總結

其實,上述所述的方法不但但省去了if else的判斷,也是目前比較流行的領域模型的一種實現方式。IAnimal是領域內對外暴露的唯一方式,外部領域(馴獸師)不需要關心任何內部實現的細節。內部的實現完全集合在IAnimal內。


分享到:


相關文章: