转载

[解锁新姿势] 回想起被 `if-else` 支配的恐惧,我们要打倒 if - else

[解锁新姿势] 兄dei,你代码需要优化了

在之前文章说到,简单 if-else ,可以使用 卫语句 进行优化。但是在实际开发中,往往不是简单 if-else 结构,我们通常会 不经意间 写下如下代码:

-------------------- 理想中的 if-else  --------------------
public void today() {
    if (isWeekend()) {
        System.out.println("玩游戏");
    } else {
        System.out.println("上班!");
    }
}


-------------------- 现实中的 if-else  --------------------

if (money >= 1000) {
    if (type == UserType.SILVER_VIP.getCode()) {

        System.out.println("白银会员 优惠50元");
        result = money - 50;
    } else if (type == UserType.GOLD_VIP.getCode()) {

        System.out.println("黄金会员 8折");
        result = money * 0.8;
    } else if (type == UserType.PLATINUM_VIP.getCode()) {

        System.out.println("白金会员 优惠50元,再打7折");
        result = (money - 50) * 0.7;
    } else {
        System.out.println("普通会员 不打折");
        result = money;
    }
}


//省略 n 个 if-else ......

复制代码

毫不夸张的说,我们都写过类似的代码,回想起被 if-else 支配的恐惧,我们常常无所下手,甚至不了了之。

下面分享一下我在开发中遇到复杂的 if-else 语句 “优雅处理” 思路。如有不妥,欢迎大家一起交流学习。

需求

假设有这么一个需求:

一个电商系统,当用户消费 满1000 金额,可以根据用户VIP等级,享受打折优惠。

根据用户VIP等级,计算出用户最终的费用。

  • 普通会员 不打折
  • 白银会员 优惠50元
  • 黄金会员 8折
  • 白金会员 优惠50元,再打7折

编码实现

private static double getResult(long money, int type) {

    double result = money;

    if (money >= 1000) {
        if (type == UserType.SILVER_VIP.getCode()) {

            System.out.println("白银会员 优惠50元");
            result = money - 50;
        } else if (type == UserType.GOLD_VIP.getCode()) {

            System.out.println("黄金会员 8折");
            result = money * 0.8;
        } else if (type == UserType.PLATINUM_VIP.getCode()) {

            System.out.println("白金会员 优惠50元,再打7折");
            result = (money - 50) * 0.7;
        } else {
            System.out.println("普通会员 不打折");
            result = money;
        }
    }

    return result;
}
复制代码

为了方便演示,代码上我进行了简单实现,但实际上 if - else 会进行 复杂的逻辑 计费。 从功能上来说,基本完成,但是对于我这种有代码洁癖的人来说,代码质量上不忍直视。我们开始着手 优化 一下我们的 第一版代码 吧。

思考

看到如上代码,聪明的朋友首先想到的是,这不是典型的 策略模式 吗?

你可真是个机灵鬼,我们先尝试用策略模式来优化一下代码吧。

策略模式

什么是策略模式?

可能有的朋友还不清楚,什么是策略模式。策略模式是定义一系列的算法,把它们一个个 封装 起来, 并且使它们可相互 替换

比如上述需求,有 返利 、有 打折 、有 折上折 等等。这些算法本身就是一种 策略 。并且这些算法可以相互 替换 的,比如今天我想让 白银会员优惠50 ,明天可以替换为 白银会员打9折

说了那么多,不如编码来得实在。

编码

public interface Strategy {
    
    // 计费方法
    double compute(long money);
}

// 普通会员策略
public class OrdinaryStrategy implements Strategy {

    @Override
    public double compute(long money) {
        System.out.println("普通会员 不打折");
        return money;
    }
}

// 白银会员策略
public class SilverStrategy implements Strategy {

    @Override
    public double compute(long money) {

        System.out.println("白银会员 优惠50元");
        return money - 50;
    }
}

// 黄金会员策略
public class GoldStrategy implements Strategy{

    @Override
    public double compute(long money) {
        System.out.println("黄金会员 8折");
        return money * 0.8;
    }
}

// 白金会员策略
public class PlatinumStrategy implements Strategy {
    @Override
    public double compute(long money) {
        System.out.println("白金会员 优惠50元,再打7折");
        return (money - 50) * 0.7;
    }
}
复制代码

我们定义来一个 Strategy 接口,并且定义 四个子类,实现接口。在对应的 compute 方法 实现自身策略的计费逻辑。

private static double getResult(long money, int type) {

    double result = money;

    if (money >= 1000) {
        if (type == UserType.SILVER_VIP.getCode()) {

            result = new SilverStrategy().compute(money);
        } else if (type == UserType.GOLD_VIP.getCode()) {

            result = new GoldStrategy().compute(money);
        } else if (type == UserType.PLATINUM_VIP.getCode()) {

            result = new PlatinumStrategy().compute(money);
        } else {
            result = new OrdinaryStrategy().compute(money);
        }
    }

    return result;
}
复制代码

然后对应 getResult 方法,根据 type 替换为对应的 用户VIP 策略 。 这里代码上出现了重复的调用 compute ,我们可以尝试进一步优化。

private static double getResult(long money, int type) {

    if (money < 1000) {
        return money;
    }

    Strategy strategy;

    if (type == UserType.SILVER_VIP.getCode()) {
        strategy = new SilverStrategy();
    } else if (type == UserType.GOLD_VIP.getCode()) {
        strategy = new GoldStrategy();
    } else if (type == UserType.PLATINUM_VIP.getCode()) {
        strategy = new PlatinumStrategy();
    } else {
        strategy = new OrdinaryStrategy();
    }

    return strategy.compute(money);
}
复制代码

还记得我在第一篇中说到的 卫语句 吗? 我们在这里把 money < 1000 的情况提前 return。更关注于 满1000逻辑 ,也可以减少不必要的缩进。

深思

我曾一度 以为 策略模式不过如此。以为代码优化到这已经可以了。

但是还有一个恐怖的事情, if-else 依然存在 :)

我尝试翻阅了许多书籍,查看如何消除 策略模式中的 if-else

书中大部分的方法是,使用简单工厂 + 策略模式。把 if - else 切换为 switch 创建一个工厂方法而已。

但是这远远没有达到我想要的效果,打倒 if - else

直到某一天夜里,我大佬在群里分享一个 Java8 小技巧时,从此大开新世界。

工厂 + 策略

public interface Strategy {

    double compute(long money);

    // 返回 type
    int getType();
}


public class OrdinaryStrategy implements Strategy {

    @Override
    public double compute(long money) {
        System.out.println("普通会员 不打折");
        return money;
    }

    // 添加 type 返回
    @Override
    public int getType() {
        return UserType.SILVER_VIP.getCode();
    }
}

public class SilverStrategy implements Strategy {

    @Override
    public double compute(long money) {

        System.out.println("白银会员 优惠50元");
        return money - 50;
    }

    // type 返回
    @Override
    public int getType() {
        return UserType.SILVER_VIP.getCode();
    }
}

....省略剩下 Strategy
复制代码

我们先在 Strategy 新增一个 getType 方法,用来 标示 该策略的 type 值。代码相对简单,这里就不过多介绍了

public class StrategyFactory {

    private Map<Integer, Strategy> map;

    public StrategyFactory() {

        List<Strategy> strategies = new ArrayList<>();

        strategies.add(new OrdinaryStrategy());
        strategies.add(new SilverStrategy());
        strategies.add(new GoldStrategy());
        strategies.add(new PlatinumStrategy());
        strategies.add(new PlatinumStrategy());

        // 看这里 看这里 看这里!
        map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
        
        /* 等同上面
        map = new HashMap<>();
        for (Strategy strategy : strategies) {
            map.put(strategy.getType(), strategy);
        }*/
    }

    public static class Holder {
        public static StrategyFactory instance = new StrategyFactory();
    }

    public static StrategyFactory getInstance() {
        return Holder.instance;
    }

    public Strategy get(Integer type) {
        return map.get(type);
    }
}
复制代码

静态内部类单例,单例模式实现的一种,不是本文重点,如不了解,可以自行 google

我们再着手创建一个 StrategyFactory 工厂类。StrategyFactory 这里我使用的是 静态内部类单例 ,在构造方法的时候,初始化好 需要的 Strategy ,并把 list 转化为 map 。 这里 转化就是 “灵魂” 所在。

toMap

我们先来看看 Java8 语法中的小技巧。

通常情况下,我们遍历 List,手动 put 到 Map 中。

--------------  before -----------------

map = new HashMap<>();
for (Strategy strategy : strategies) {
    map.put(strategy.getType(), strategy);
}

--------------  after Java8 -----------------

map = strategies.stream().collect(Collectors.toMap(Strategy::getType, strategy -> strategy));
复制代码

toMap 第一个参数是一个Function,对应 Map 中的 key ,第二个参数也是一个Function,strategy -> strategy, 左边 strategy 是遍历 strategies 中的每一个strategy,右边 strategy 则是 Map 对应 value 值。

若是不了解 Java8 语法的朋友,强烈建议看 《 Java8 实战 》,书中详细的介绍了 Lambda 表达式、 Stream 等语法。

效果

private static double getResult(long money, int type) {

    if (money < 1000) {
        return money;
    }

    Strategy strategy = StrategyFactory.getInstance().get(type);
    
    if (strategy == null){
        throw new IllegalArgumentException("please input right type");
    }

    return strategy.compute(money);
}
复制代码

至此,通过一个工厂类,在我们在 getResult() 调用的时候,根据传入 type ,即可获取到 对应 Strategy

再也没有可怕的 if-else 语句。

完结撒花撒花 : )

后续

后续代码优化上,若是 Java 项目,可以尝试使用 自定义注解 ,注解 Strategy 实现类。

这样可以简化原来需在工厂类 List 添加一个 Stratey 策略

最后

以上就是我在开发中遇到复杂的 if-else 语句 “优雅处理” 思路,如有不妥,欢迎大家一起交流学习。

原文  https://juejin.im/post/5def654f51882512302daeef
正文到此结束
Loading...