消除又臭又长的if...else判断的技巧,帮你实现更优雅的编程!


prtyaa
prtyaa 2023-12-31 22:19:23 64162
分类专栏: 资讯

最近在做代码重构,发现了很多代码的烂味道。其他的不多说,今天主要说说那些又臭又长的if...else要如何重构。

在介绍更更优雅的编程之前,让我们一起回顾一下,不好的if...else代码

一、又臭又长的if...else

废话不多说,先看看下面的代码。

public interface IPay {
 
    void pay();
}
 
 
@Service
public class AliaPay implements IPay {
 
 
    @Override
    public void pay() {
        System.out.println("===发起支付宝支付===");
    }
}
 
 
@Service
public class WeixinPay implements IPay {
 
    @Override
    public void pay() {
        System.out.println("===发起微信支付===");
    }
}
 
 
@Service
public class JingDongPay implements IPay {
 
    @Override
    public void pay() {
        System.out.println("===发起京东支付===");
    }
}

~

@Service
public class PayService {
 
    @Autowired
    private AliaPay aliaPay;
    @Autowired
    private WeixinPay weixinPay;
    @Autowired
    private JingDongPay jingDongPay;
 
 
    public void toPay(String code) {
        if ("alia".equals(code)) {
            aliaPay.pay();
        } else if ("weixin".equals(code)) {
            weixinPay.pay();
        } else if ("jingdong".equals(code)) {
            jingDongPay.pay();
        } else {
            System.out.println("找不到支付方式");
        }
    }
}

PayService类的toPay方法主要是为了发起支付,根据不同的code,决定调用用不同的支付类(比如:aliaPay)的pay方法进行支付。


这段代码有什么问题呢?也许有些人就是这么干的。

试想一下,如果支付方式越来越多,比如:又加了百度支付、美团支付、银联支付等等,就需要改toPay方法的代码,增加新的else...if判断,判断多了就会导致逻辑越来越多?


很明显,这里违法了设计模式六大原则的:开闭原则 和 单一职责原则

开闭原则:对扩展开放,对修改关闭。就是说增加新功能要尽量少改动已有代码。
单一职责原则:顾名思义,要求逻辑尽量单一,不要太复杂,便于复用。

那有什么办法可以解决这个问题呢?

二、使用注解

代码中之所以要用code判断使用哪个支付类,是因为code和支付类没有一个绑定关系,如果绑定关系存在了,就可以不用判断了。

我们先定义一个注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PayCode {
 
    String value();
 
    String name();
}

然后在所有的支付类上都加上注解

@PayCode(value = "alia", name = "支付宝支付")
@Service
public class AliaPay implements IPay {
 
 
    @Override
    public void pay() {
        System.out.println("===发起支付宝支付===");
    }
}
 
 
 
@PayCode(value = "weixin", name = "微信支付")
@Service
public class WeixinPay implements IPay {
 
    @Override
    public void pay() {
        System.out.println("===发起微信支付===");
    }
}
 
 
@PayCode(value = "jingdong", name = "京东支付")
@Service
public class JingDongPay implements IPay {
 
    @Override
    public void pay() {
        System.out.println("===发起京东支付===");
    }
}

然后增加最关键的类:

@Service
public class PayService2 implements ApplicationListener<ContextRefreshedEvent> {
 
    private static Map<String, IPay> payMap = null;
 
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
        Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(PayCode.class);
 
        if (beansWithAnnotation != null) {
            payMap = new HashMap<>();
            beansWithAnnotation.forEach((key, value) -> {
                String bizType = value.getClass().getAnnotation(PayCode.class).value();
                payMap.put(bizType, (IPay) value);
            });
        }
    }
 
    public void pay(String code) {
        payMap.get(code).pay();
    }
}

PayService2类实现了ApplicationListener接口,这样在onApplicationEvent方法中,就可以拿到ApplicationContext的实例。我们再获取打了PayCode注解的类,放到一个map中,map中的key就是PayCode注解中定义的value,跟code参数一致,value是支付类的实例。

这样,每次就可以每次直接通过code获取支付类实例,而不用if...else判断了。如果要加新的支付方法,只需在支付类上面打上PayCode注解定义一个新的code即可。

注意:这种方式的code可以没有业务含义,可以是纯数字,只有不重复就行。

动图封面
 

三、动态拼接名称

再看看这种方法,主要针对code是有业务含义的场景。

@Service
public class PayService3 implements ApplicationContextAware {
 
    private ApplicationContext applicationContext;
 
    private static final String SUFFIX = "Pay";
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    public void toPay(String payCode) {
        ((IPay) applicationContext.getBean(getBeanName(payCode))).pay();
    }
 
    public String getBeanName(String payCode) {
        return payCode + SUFFIX;
    }
 
}

我们可以看到,支付类bean的名称是由code和后缀拼接而成,比如:aliaPay、weixinPay和jingDongPay。这就要求支付类取名的时候要特别注意,前面的一段要和code保持一致。调用的支付类的实例是直接从ApplicationContext实例中获取的,默认情况下bean是单例的,放在内存的一个map中,所以不会有性能问题。

特别说明一下,这种方法实现了ApplicationContextAware接口跟上面的ApplicationListener接口不一样,是想告诉大家获取ApplicationContext实例的方法不只一种。

动图封面
 

四、模板方法判断

当然除了上面介绍的两种方法之外,spring的源码实现中也告诉我们另外一种思路,解决if...else问题。

我们先一起看看spring AOP的部分源码,看一下DefaultAdvisorAdapterRegistrywrap方法

public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
    if (adviceObject instanceof Advisor) {
      return (Advisor) adviceObject;
    }
    if (!(adviceObject instanceof Advice)) {
      throw new UnknownAdviceTypeException(adviceObject);
    }
    Advice advice = (Advice) adviceObject;
    if (advice instanceof MethodInterceptor) {
      // So well-known it doesn't even need an adapter.
      return new DefaultPointcutAdvisor(advice);
    }
    for (AdvisorAdapter adapter : this.adapters) {
      // Check that it is supported.
      if (adapter.supportsAdvice(advice)) {
        return new DefaultPointcutAdvisor(advice);
      }
    }
    throw new UnknownAdviceTypeException(advice);
  }

重点看看supportAdvice方法,有三个类实现了这个方法。我们随便抽一个类看看

class AfterReturningAdviceAdapter implements AdvisorAdapter, Serializable {
 
  @Override
  public boolean supportsAdvice(Advice advice) {
    return (advice instanceof AfterReturningAdvice);
  }
 
  @Override
  public MethodInterceptor getInterceptor(Advisor advisor) {
    AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();
    return new AfterReturningAdviceInterceptor(advice);
  }
 
}

该类的supportsAdvice方法非常简单,只是判断了一下advice的类型是不是AfterReturningAdvice。

我们看到这里应该有所启发。

其实,我们可以这样做,定义一个接口或者抽象类,里面有个support方法判断参数传的code是否自己可以处理,如果可以处理则走支付逻辑。

public interface IPay {
 
    boolean support(String code);
 
    void pay();
}
 
 
@Service
public class AliaPay implements IPay {
 
    @Override
    public boolean support(String code) {
        return "alia".equals(code);
    }
 
    @Override
    public void pay() {
        System.out.println("===发起支付宝支付===");
    }
}
 
 
@Service
public class WeixinPay implements IPay {
 
 
    @Override
    public boolean support(String code) {
        return "weixin".equals(code);
    }
 
    @Override
    public void pay() {
        System.out.println("===发起微信支付===");
    }
}
 
 
@Service
public class JingDongPay implements IPay {
    @Override
    public boolean support(String code) {
        return "jingdong".equals(code);
    }
 
    @Override
    public void pay() {
        System.out.println("===发起京东支付===");
    }
}

每个支付类都有一个support方法,判断传过来的code是否和自己定义的相等。

@Service
public class PayService4 implements ApplicationContextAware, InitializingBean {
 
    private ApplicationContext applicationContext;
 
    private List<IPay> payList = null;
 
    @Override
    public void afterPropertiesSet() throws Exception {
        if (payList == null) {
            payList = new ArrayList<>();
            Map<String, IPay> beansOfType = applicationContext.getBeansOfType(IPay.class);
 
            beansOfType.forEach((key, value) -> payList.add(value));
        }
    }
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    public void toPay(String code) {
        for (IPay iPay : payList) {
            if (iPay.support(code)) {
                iPay.pay();
            }
        }
    }
 
}

这段代码中先把实现了IPay接口的支付类实例初始化到一个list集合中,返回在调用支付接口时循环遍历这个list集合,如果code跟自己定义的一样,则调用当前的支付类实例的pay方法。

动图封面
 

五、其他的消除if...else的方法

当然实际项目开发中使用if...else判断的场景非常多,上面只是其中几种场景。下面再列举一下,其他常见的场景。

1.根据不同的数字返回不同的字符串

public String getMessage(int code) {
    if (code == 1) {
        return "成功";
    } else if (code == -1) {
        return "失败";
    } else if (code == -2) {
        return "网络超时";
    } else if (code == -3) {
        return "参数错误";
    }
    throw new RuntimeException("code错误");
}

其实,这种判断没有必要,用一个枚举就可以搞定。

public enum MessageEnum {
 
    SUCCESS(1, "成功"),
    FAIL(-1, "失败"),
    TIME_OUT(-2, "网络超时"),
    PARAM_ERROR(-3, "参数错误");
 
    private int code;
    private String message;
 
    MessageEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
 
    public int getCode() {
        return this.code;
    }
 
    public String getMessage() {
        return this.message;
    }
 
    public static MessageEnum getMessageEnum(int code) {
        return Arrays.stream(MessageEnum.values()).filter(x -> x.code == code).findFirst().orElse(null);
    }
}

再把调用方法稍微调整一下

public String getMessage(int code) {
    MessageEnum messageEnum = MessageEnum.getMessageEnum(code);
    return messageEnum.getMessage();
}

完美。

2.集合中的判断

上面的枚举MessageEnum中的getMessageEnum方法,如果不用java8的语法的话,可能要这样写

public static MessageEnum getMessageEnum(int code) {
    for (MessageEnum messageEnum : MessageEnum.values()) {
        if (code == messageEnum.code) {
            return messageEnum;
        }
    }
    return null;
}

对于集合中过滤数据,或者查找方法,java8有更简单的方法消除if...else判断。

public static MessageEnum getMessageEnum(int code) {
    return Arrays.stream(MessageEnum.values()).filter(x -> x.code == code).findFirst().orElse(null);
}

3.简单的判断

其实有些简单的if...else完全没有必要写,可以用三目运算符代替,比如这种情况:

public String getMessage2(int code) {
    if(code == 1) {
        return  "成功";
    }
    return "失败";
}

改成三目运算符:

public String getMessage2(int code) {
    return code == 1 ? "成功" : "失败";
}

修改之后代码更简洁一些。

4.判断是否为null

java中自从有了null之后,很多地方都要判断实例是否为null,不然可能会出现NPE的异常。

  public String getMessage2(int code) {
      return code == 1 ? "成功" : "失败";
  }
 
  public String getMessage3(int code) {
      Test test = null;
          return test.getMessage2(1);
  }

这里如果不判断异常的话,就会出现NPE异常。我们只能老老实实加上判断。

public String getMessage3(int code) {
    Test test = null;
    if (test != null) {
        return test.getMessage2(1);
    }
    return null;
}

有没有其他更优雅的处理方式呢?

public String getMessage3(int code) {
    Test test = null;
    Optional<Test> testOptional = Optional.of(test);
    return testOptional.isPresent() ? testOptional.get().getMessage2(1) : null;
}

答案是使用Optional

当然,还有很多其他的场景可以优化if...else,我再这里就不一一介绍了,感兴趣的朋友可以给我留言,一起探讨和研究一下。

网站声明:如果转载,请联系本站管理员。否则一切后果自行承担。

本文链接:https://www.xckfsq.com/news/show.html?id=33379
赞同 0
评论 0 条
prtyaaL1
粉丝 1 发表 2554 + 关注 私信
上周热门
银河麒麟添加网络打印机时,出现“client-error-not-possible”错误提示  1504
银河麒麟打印带有图像的文档时出错  1423
银河麒麟添加打印机时,出现“server-error-internal-error”  1213
统信操作系统各版本介绍  1134
统信桌面专业版【如何查询系统安装时间】  1131
统信桌面专业版【全盘安装UOS系统】介绍  1086
麒麟系统也能完整体验微信啦!  1043
统信【启动盘制作工具】使用介绍  692
统信桌面专业版【一个U盘做多个系统启动盘】的方法  635
信刻全自动档案蓝光光盘检测一体机  542
本周热议
我的信创开放社区兼职赚钱历程 40
今天你签到了吗? 27
信创开放社区邀请他人注册的具体步骤如下 15
如何玩转信创开放社区—从小白进阶到专家 15
方德桌面操作系统 14
我有15积分有什么用? 13
用抖音玩法闯信创开放社区——用平台宣传企业产品服务 13
如何让你先人一步获得悬赏问题信息?(创作者必看) 12
2024中国信创产业发展大会暨中国信息科技创新与应用博览会 9
中央国家机关政府采购中心:应当将CPU、操作系统符合安全可靠测评要求纳入采购需求 8

添加我为好友,拉您入交流群!

请使用微信扫一扫!