如何优雅地优化代码中的的if else和switch

引言

一般来说,随着我们项目的迭代以及业务的越来越复杂,项目中的分支判断会原来越多。当项目中涉及到复杂的业务判断或者分支逻辑时,我们就需要考虑是否需要对项目进行重构了,或者if else和switch case是否能够满足当前项目的复杂度。

我们举一个简单的例子,假如我们是马戏团的老板,在训练一些动物去做一些指令,刚开始很简单,只训练了一条狗,当狗握了一下手后,给她奖励一些狗粮。这样慢慢地小狗就学会了握手。

我们先定义一条小狗对象,小狗做了某些事情(“握手”)后,可以得到一些奖励

public class Dog {
    public void train(){
        System.out.println("握手");
    }
    public void getReward(){
        System.out.println("狗粮");
    }
}

定义驯兽师,通过train来训练动物

public class Beast {
    /**
     * 训练
     */
    public void train(Dog dog){
        /**
         * 狗狗做了一些事情
         */
        dog.train();
        /**
         * 狗狗得到奖励
         */
        dog.getReward();
    }
}

如果我们只需要训练一条动物,那么相对来说比较简单。但是后来马戏团又引进了一头狮子,需要训练狮子钻火圈,因此,为了区分狗和狮子,我们增加了一种类型区分训练的动物是狗还是狮子。

public class Lion {
    public void train(){
        System.out.println("钻火圈");
    }
    public void getReward(){
        System.out.println("得到一只鸡");
    }
}

重新修改Beast类,使其既可以训练小狗,又可以训练狮子

public class Beast {
    
    public void train(Object animal, int type){
        if (type == 1){
            trainDog((Dog)animal);
        }else if (type == 2){
            trainLion((Lion)animal);
        }
    }
    
    /**
     * 训练
     */
    public void trainDog(Dog dog){
        /**
         * 狗狗做了一些事情
         */
        dog.train();
        /**
         * 狗狗得到奖励
         */
        dog.getReward();
    }
    /**
     * 训练
     */
    public void trainLion(Lion lion){
        /**
         * 狗狗做了一些事情
         */
        lion.train();
        /**
         * 狗狗得到奖励
         */
        lion.getReward();
    }
}

我们通过type类型来区分训练的动物类型,后来马戏团引来了越来越多的动物,那我们的type的取值会越来越多:1表示狗,2表示狮子,3表示猫,4表示老虎,5表示猴子...等等。而且随着系统越来越复杂,我们在训练之前不同的动物还需要做不同的准备工作。当然,目前的系统只要增加if else或者switch case是可以满足需求的,但这样写显得不是太优雅,我们希望找到一种比较优雅比较有设计感的方式来取代if else或者switch case

优化if else

在做优化之前,我们需要先弄清楚我们的目的。我们是马戏团的驯兽师,目的是训练动物。

  • 目的:训练动物做一些事情(doSomething)

  • 方式:通过奖励(getReward)诱导动物进行训练。

有了上面的目的之后,我们就可以定义一个模型animal

public interface IAnimal {

    /**
     * 获取动物种类
     * @return
     */
    int getType();
    
    /**
     * 训练动作
     */
    void train();
}

Animal只暴露两个方法,其中doSomething是专门用来训练动物的,至于如何训练全都由子类实现。

  • getType():用来区分不同的动物
  • doSomething():训练动物

然后我们定义一个子类实现这个接口,用来具体化如何训练动物

public abstract class AbsTrainAnimal implements IAnimal{

    /**
     * 训练前需要做的准备
     */
    abstract void beforeTrain();

    /**
     * 训练后需要做的准备
     */
    abstract void afterTrain();

    /**
     * 训练出现异常需要做的
     */
    abstract void exceptionTrain(Throwable throwable);

    /**
     * 具体训练
     */
    abstract void doSomething();
    
    /**
     * 训练动作
     */
    @Override
    public final void train() {
        try {
            beforeTrain();
            doSomething();
            afterTrain();
        }catch (Throwable throwable){
            exceptionTrain(throwable);
        }
    }
}

我们定义了一个抽象类用来实现IAnimal接口,作为所有动物训练的基类。其中实现的接口train使用了final进行了限制,防止子类对其进行覆盖操作。

在AbsTrainAnimal中,我们对train()进行了各种功能的细化

  • doSomething:具体训练的内容
  • beforeTrain:训练之前需要做的一些准备
  • afterTrain:训练之后需要做的事情
  • exceptionTrain:训练中发生意外应该如何处理

因为所有动物的以上四个方法可能都不相同,所以我们声明为abstract方法,方便子类自己实现。基于以上设计,我们就可以定义一个Dog类,对其进行训练。

public class Dog extends AbsTrainAnimal {
    /**
     * 训练前需要做的准备
     */
    @Override
    void beforeTrain() {
        System.out.println("抚摸额头以示鼓励");
    }

    /**
     * 训练后需要做的准备
     */
    @Override
    void afterTrain() {
        System.out.println("奖励一些狗粮");
    }

    /**
     * 训练出现异常需要做的
     *
     * @param throwable
     */
    @Override
    void exceptionTrain(Throwable throwable) {
        System.out.println("出去罚站");
    }

    /**
     * 具体训练
     */
    @Override
    void doSomething() {
        System.out.println("握手");
    }

    /**
     * 获取动物种类
     *
     * @return
     */
    @Override
    public int getType() {
        return 1;
    }
}

同时定义一个狮子Lion

public class Lion extends AbsTrainAnimal {
    /**
     * 训练前需要做的准备
     */
    @Override
    void beforeTrain() {
        System.out.println("友好交流");
    }

    /**
     * 训练后需要做的准备
     */
    @Override
    void afterTrain() {
        System.out.println("奖励一只鸡");
    }

    /**
     * 训练出现异常需要做的
     *
     * @param throwable
     */
    @Override
    void exceptionTrain(Throwable throwable) {
        System.out.println("紧急送往医院");
    }

    /**
     * 具体训练
     */
    @Override
    void doSomething() {
        System.out.println("钻火圈");
    }

    /**
     * 获取动物种类
     *
     * @return
     */
    @Override
    public int getType() {
        return 2;
    }
}

我们可以看到,Dog和Lion的动物种类是不一样的,Dog为1,Lion为2。我们可以根据type区分是狮子还是狗。但为了避免使用if else进行区分,我们需要一个工厂类来生产这两种动物。

@Service
public class AnimalFactory {
    
    private static List<Class<? extends IAnimal>> animalLists = Lists.newArrayList();
    private static Map<Integer, IAnimal> animalMaps = Maps.newHashMap();
    
    static {
        animalLists.add(Dog.class);
        animalLists.add(Lion.class);
    }
    
    @PostConstruct
    public void init() throws IllegalAccessException, InstantiationException {
        for (Class<? extends IAnimal> clazz : animalLists){
            Object obj = clazz.newInstance();
            animalMaps.put(obj.getType(), obj);
        }
    }

    /**
     * 构建动物类
     * @param type
     * @return
     */
    IAnimal build(int type){
        return animalMaps.get(type);
    }
}

我们有了这个工厂类,就可以根据不同的动物类型获取不同的对象,并对其进行训练。当然我们这里都是使用的单例模式,每个对象只对应一个实例,如果每次都生成不同的实例,可以对其进行简单的改造即可实现。

我们再重写驯兽师Beast类

@Service
public class Beast {

    @Resource
    private AnimalFactory animalFactory;

    /**
     * 训练动物,只需要知道动物的类型即可
     * @param type
     */
    public void train(int type){
        IAnimal animal = animalFactory.build(type);
        animal.train();
    }
}

可以看到train方法只需要关系动物类型即可,不需要再根据type进行判断动物类型在对其进行不同的操作。如果有新的动物加入,只需要实现AbsTrainAnimal基类,然后向AnimalFactory进行注册即可。避免了根据不同type进行if else或者switch的判断

总结

其实,上述所述的方法不但但省去了if else的判断,也是目前比较流行的领域模型的一种实现方式。IAnimal是领域内对外暴露的唯一方式,外部领域(驯兽师)不需要关心任何内部实现的细节。内部的实现完全集合在IAnimal内。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容