• 探讨复杂的 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 依然存在 :)

    我尝试翻阅了许多书籍,查看如何消除 策略模式中的 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 语句“优雅处理”思路,如有不妥,欢迎大家一起交流学习。

  • 相关阅读:
    VINTF
    Excel 公式
    SSIS ODBC方式连接mysql数据库
    SSIS错误汇总
    linux防火墙(转)
    如何查询域名的MX、A、DNS、txt、cname记录
    IP反向解析
    Visual Studio 内存泄漏检测方法
    strcpy慎用
    main函数前后执行代码
  • 原文地址:https://www.cnblogs.com/ldsweely/p/12058346.html
Copyright © 2020-2023  润新知