复杂 if-else 语句优雅处理思路之策略模式的应用


前言


对于简单 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 依然存在。

工厂 + 策略


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));
    }

    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

若是不了解 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 语句。


文章作者: 筱杉少年
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 筱杉少年 !
评论
 上一篇
Spring Security 教程01:开篇 Spring Security 教程01:开篇
在 Spring Boot 或者 Spring Cloud 中,如果想选择一个权限管理框架,几乎毫无疑问的选择 Spring Security,Shiro 在这个环境下已经不具备优势了。
下一篇 
Spring Boot 开发中跨域场景及其解决方案介绍 Spring Boot 开发中跨域场景及其解决方案介绍
很多人对跨域有一种误解,认为这是前端的事,和后端没关系,其实不是这样的。本文就和大家聊一下 Spring Boot 开发中三种跨域场景及其解决方案。
2020-05-12
  目录