⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.im/post/5def654f51882512302daeef 「hyzhan43」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

在之前文章说到,简单 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 语句“优雅处理”思路,如有不妥,欢迎大家一起交流学习。

文章目录
  1. 1. 前言
  2. 2. 需求
  3. 3. 编码实现
  4. 4. 思考
  5. 5. 策略模式
    1. 5.1. 什么是策略模式?
    2. 5.2. 编码
  6. 6. 深思
  7. 7. 工厂 + 策略
  8. 8. toMap
  9. 9. 效果
  10. 10. 后续
  11. 11. 最后