工厂+策略解决多重if-else

工厂+策略解决多重if-else

解决多个if-else

参考文献:https://mp.weixin.qq.com/s/UyiqGjPb9K5f7XwlfsrXBw

在之前文章说到,简单 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 策略。

代码:

/** * 策略可以理解成一种算法 */
public interface Strategy {
   
        //用户享受折扣
        double comput(double money);

        //用户类型 1 表示普通用户 2 表示白银用户 3 表示黄金用户
        Integer getType();
}
/** * 普通会员策略 */
public class OrdinaryStrategy implements Strategy{
   
    @Override
    public double comput(double money) {
   
        System.out.println("普通会员。。。享受九折折扣");
        return money * 0.9;
    }

    @Override
    public Integer getType() {
   
        return UserType.ordinaryStrategy;
    }
}
/** * 白银会员 */
public class SilverStrategy implements Strategy{
   

    @Override
    public double comput(double money) {
   
        System.out.println("白银会员 享受折扣为8折");
        return money * 0.8;
    }

    @Override
    public Integer getType() {
   
        return UserType.silverStrategy;
    }
}
/** * 黄金会员 */
public class GoldStrategy implements Strategy {
   
    @Override
    public double comput(double money) {
   
        System.out.println("黄金会员 享受的折扣是 7折");
        return money * 0.7;
    }

    @Override
    public Integer getType() {
   
        return UserType.GOLD_STRATEGY;
    }
}
public interface   UserType {
   
    //普通用户
    public static final Integer ordinaryStrategy = 1;

    //白银用户
    public static final Integer silverStrategy = 2;

    //黄金用户
    public static final Integer GOLD_STRATEGY = 3;


    //白金用户
}
import java.util.*;
import java.util.stream.Collectors;

/** * 策略工厂 */
public class StrategyFactory {
   
    private Map<Integer,Strategy> map = new HashMap<>();

    public StrategyFactory(){
   
        List<Strategy> strategyList = new ArrayList<>();
        strategyList.add(new OrdinaryStrategy());
        strategyList.add(new SilverStrategy());
        strategyList.add(new GoldStrategy());



        map = strategyList.stream().collect(Collectors.toMap(Strategy::getType,strategy -> strategy));

      /* //遍历集合。map 的key 是usertype value:strategy for (Strategy strategy : strategyList) { // System.out.println(strategy.getType()); map.put(strategy.getType(),strategy); }*/
    }

    //静态内部类的单利
    public static class Hold{
   
        public static StrategyFactory install = new StrategyFactory();
    }

    //初始化工厂
    public static StrategyFactory getStrategyFactory(){
   
        return Hold.install;
    }

    //获取策略
    public Strategy getType(Integer type){
   
         return map.get(type);
    }


}
import com.tubang.user.Strategy;
import com.tubang.user.StrategyFactory;

public class TestStrategy {

    public static void main(String[] args) {


        //System.out.println("普通会员购买 100:"+buy(1,2000));
        // System.out.println("白银会员购买 2000,折扣后的价格:"+buy(2,2000));
         System.out.println("黄金会员购买 2000,折扣后的价格:"+buy(3,2000));


    }

    public static double buy(Integer type, double money){
        if(money <= 1000){
            return money;
        }

        Strategy strategy = StrategyFactory.getStrategyFactory().getType(type);
        if(strategy == null){
            throw new RuntimeException("用户类型错误!!!");
        }
        return strategy.comput(money);
    }

}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/100759.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • JAVA 日期格式化

    最近项目中需要用到SimpleDateFormat格式化日期,但是因为对日期格式的不熟练多花了十分钟左右的时间在日期格式化上面,所以趁着周末外面下着大雨闲着无聊整理一下日期格式问题。日期格式化学习预定义格式日期格式化样式自定义日期格式化预定义格式java提供了预定义的日期格式,预定义的日期格式可以满足绝大多数的场景需要。格式化日期需要两步:1、通过DateFormat.getDate…

    2022年4月6日
    51
  • java发送邮件代码[通俗易懂]

    java发送邮件代码[通俗易懂]java发送邮件首先需要通过对应的邮件服务转发到中间基站,再有接收方服务器接收邮件,转发给收件人,因此我们再发送邮件时需要先设定邮件发出服务(例如qq、网易等),在配置邮件发送协议以及发送人和接收人,最后设置邮件内容,此处我以正文中带有图片的代码为例!编写代码前,我们需要准备javax下的mail包和activation包。这个在文章的末位会提供下载地址,好了天冷屁股凉,我们直接上高速。。importjavax.mail.Authenticator;importjavax.mail.Mess

    2022年5月14日
    42
  • settime和setinterval_setinterval是异步还是同步

    settime和setinterval_setinterval是异步还是同步setTimeout和setInterval的语法相同。它们都有两个参数,一个是将要执行的代码字符串,还有一个是以毫秒为单位的时间间隔,当过了那个时间段之后就将执行那段代码。这两个函数的区别是:setInterval在执行完一次代码之后,经过了那个固定的时间间隔,它还会自动重复执行代码,而setTimeout只执行一次那段代码。有一次我没有分清,使用了setInterval,导致电脑崩溃了。setTimeout表面上是只执行一次,只是起到延迟作用。但是也可以通过创建一个函数循环重复调用…

    2022年10月4日
    2
  • windows系统如何cmd查看端口被占用、杀进程「建议收藏」

    windows系统如何cmd查看端口被占用、杀进程「建议收藏」首先是启动windows的命令窗口,按键盘上的windows+R,然后在输入框中输入cmd,既可以启动命令窗口 进入windows命令窗口之后,输入命令,输入netstat-ano然后回车,就可以看到系统当前所有的端口使用情况。 通过命令查找某一特定端口,在命令窗口中输入命令中输入netstat-ano|findstr”端口号”,然后回车就可以看到这个端口被哪个应用占用。 查看到对应的进程id之后,就可以通过id查找对应的进程名称,使用命令tasklist|findstr”进程id..

    2022年5月18日
    47
  • Alex 的 Hadoop 菜鸟教程: 第17课 更快速的MapReduce – Spark「建议收藏」

    Alex 的 Hadoop 菜鸟教程: 第17课 更快速的MapReduce – Spark「建议收藏」Spark是Apache的顶级项目。项目背景是Hadoop的MapReduce太挫太慢了,于是有人就做了Spark,目前Spark声称在内存中比Hadoop快100倍,在磁盘上比Hadoop快10倍。

    2022年5月11日
    45
  • springboot—@Async实现异步调用及异步回调Future「建议收藏」

    springboot—@Async实现异步调用及异步回调Future「建议收藏」异步调用相对的是同步调用。同步方法调用的时候必须是按照顺序执行的,上一行代码执行完,才会执行下一行。而异步方法调用是相当于多个线程执行,不需要等待上一行代码的执行结果。首先测试方法同步的情况:controller:packagespringboot_async.async_test;importorg.springframework.beans.factory.annot…

    2022年7月27日
    39

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号