一旦代码中if-else过多,就会大大的影响其可读性和可维护性。web
首先可读性,不言而喻,过多的if-else代码和嵌套,会使阅读代码的人很难理解究竟是什么意思。尤为是那些没有注释的代码。算法
其次是可维护性,由于if-else特别多,想要新加一个分支的时候,就会很难添加,极其容易影响到其余的分支。数据库
笔者曾经看到过一个支付的核心应用,这个应用支持了不少业务的线上支付功能,可是每一个业务都有不少定制的需求,因此不少核心的代码中都有一大坨if-else。设计模式
每一个新业务须要定制的时候,都把本身的if放到整个方法的最前面,以保证本身的逻辑能够正常执行。这种作法,后果可想而知。bash
其实,if-else是有办法能够消除掉的,其中比较典型的而且使用普遍的就是借助策略模式和工厂模式,准确的说是利用这两个设计模式的思想,完全消灭代码中的if-else。框架
本文,就结合这两种设计模式,介绍如何消除if-else,而且,还会介绍如何和Spring框架结合,这样读者看完本文以后就能够当即应用到本身的项目中。ide
本文涉及到一些代码,可是做者尽可能用通俗的例子和伪代码等形式使内容不那么枯燥。学习
假设咱们要作一个外卖平台,有这样的需求:优化
一、外卖平台上的某家店铺为了促销,设置了多种会员优惠,其中包含超级会员折扣8折、普通会员折扣9折和普通用户没有折扣三种。ui
二、但愿用户在付款的时候,根据用户的会员等级,就能够知道用户符合哪一种折扣策略,进而进行打折,计算出应付金额。
三、随着业务发展,新的需求要求专属会员要在店铺下单金额大于30元的时候才能够享受优惠。
四、接着,又有一个变态的需求,若是用户的超级会员已经到期了,而且到期时间在一周内,那么就对用户的单笔订单按照超级会员进行折扣,并在收银台进行强提醒,引导用户再次开通会员,并且折扣只进行一次。
那么,咱们能够看到如下伪代码:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (用户是专属会员) {
if (订单金额大于30元) {
returen 7折价格;
}
}
if (用户是超级会员) {
return 8折价格;
}
if (用户是普通会员) {
if(该用户超级会员刚过时而且还没有使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
return 原价;
}复制代码
以上,就是对于这个需求的一段价格计算逻辑,使用伪代码都这么复杂,若是是真的写代码,那复杂度可想而知。
这样的代码中,有不少if-else,而且还有不少的if-else的嵌套,不管是可读性仍是可维护性都很是低。
那么,如何改善呢?
接下来,咱们尝试引入策略模式来提高代码的可维护性和可读性。
首先,定义一个接口:
/**
* @author mhcoding
*/
public interface UserPayService {
/**
* 计算应付价格
*/
public BigDecimal quote(BigDecimal orderPrice);
}复制代码
接着定义几个策略类:
/**
* @author mhcoding
*/
public class ParticularlyVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
}
public class SuperVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
}
public class VipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过时而且还没有使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
}复制代码
引入了策略以后,咱们能够按照以下方式进行价格计算:
/**
* @author mhcoding
*/
public class Test {
public static void main(String[] args) {
UserPayService strategy = new VipPayService();
BigDecimal quote = strategy.quote(300);
System.out.println("普通会员商品的最终价格为:" + quote.doubleValue());
strategy = new SuperVipPayService();
quote = strategy.quote(300);
System.out.println("超级会员商品的最终价格为:" + quote.doubleValue());
}
}复制代码
以上,就是一个例子,能够在代码中new出不一样的会员的策略类,而后执行对应的计算价格的方法。这个例子以及策略模式的相关知识,读者能够在《如何给女友解释什么是策略模式?》一文中学习。
可是,真正在代码中使用,好比在一个web项目中使用,上面这个Demo根本没办法直接用。
首先,在web项目中,上面咱们建立出来的这些策略类都是被Spring托管的,咱们不会本身去new一个实例出来。
其次,在web项目中,若是真要计算价格,也是要事先知道用户的会员等级,好比从数据库中查出会员等级,而后根据等级获取不一样的策略类执行计算价格方法。
那么,web项目中真正的计算价格的话,伪代码应该是这样的:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
if (vipType == 专属会员) {
//伪代码:从Spring中获取超级会员的策略对象
UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 超级会员) {
UserPayService strategy = Spring.getBean(SuperVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 普通会员) {
UserPayService strategy = Spring.getBean(VipPayService.class);
return strategy.quote(orderPrice);
}
return 原价;
}复制代码
经过以上代码,咱们发现,代码可维护性和可读性好像是好了一些,可是好像并无减小if-else啊。
其实,在以前的《如何给女友解释什么是策略模式?》一文中,咱们介绍了不少策略模式的优势。可是,策略模式的使用上,仍是有一个比较大的缺点的:
客户端必须知道全部的策略类,并自行决定使用哪个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
也就是说,虽然在计算价格的时候没有if-else了,可是选择具体的策略的时候仍是不可避免的仍是要有一些if-else。
另外,上面的伪代码中,从Spring中获取会员的策略对象咱们是伪代码实现的,那么代码到底该如何获取对应的Bean呢?
接下来咱们看如何借助Spring和工厂模式,解决上面这些问题。
为了方便咱们从Spring中获取UserPayService的各个策略类,咱们建立一个工厂类:
/**
* @author mhcoding
*/
public class UserPayServiceStrategyFactory {
private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();
public static UserPayService getByUserType(String type){
return services.get(type);
}
public static void register(String userType,UserPayService userPayService){
Assert.notNull(userType,"userType can't be null");
services.put(userType,userPayService);
}
}复制代码
这个UserPayServiceStrategyFactory中定义了一个Map,用来保存全部的策略类的实例,并提供一个getByUserType方法,能够根据类型直接获取对应的类的实例。还有一个register方法,这个后面再讲。
有了这个工厂类以后,计算价格的代码便可获得大大的优化:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType);
return strategy.quote(orderPrice);
}复制代码
以上代码中,再也不须要if-else了,拿到用户的vip类型以后,直接经过工厂的getByUserType方法直接调用就能够了。
经过策略+工厂,咱们的代码很大程度的优化了,大大提高了可读性和可维护性。
可是,上面还遗留了一个问题,那就是UserPayServiceStrategyFactory中用来保存全部的策略类的实例的Map是如何被初始化的?各个策略的实例对象如何塞进去的呢?
还记得咱们前面定义的UserPayServiceStrategyFactory中提供了的register方法吗?他就是用来注册策略服务的。
接下来,咱们就想办法调用register方法,把Spring经过IOC建立出来的Bean注册进去就好了。
这种需求,能够借用Spring种提供的InitializingBean接口,这个接口为Bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
那么,咱们将前面的各个策略类稍做改造便可:
/**
* @author mhcoding
*/
@Service
public class ParticularlyVipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("ParticularlyVip",this);
}
}
@Service
public class SuperVipPayService implements UserPayService ,InitializingBean{
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("SuperVip",this);
}
}
@Service
public class VipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过时而且还没有使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("Vip",this);
}
}复制代码
只须要每个策略服务的实现类都实现InitializingBean接口,并实现其afterPropertiesSet方法,在这个方法中调用UserPayServiceStrategyFactory.register便可。
这样,在Spring初始化的时候,当建立VipPayService、SuperVipPayService和ParticularlyVipPayService的时候,会在Bean的属性初始化以后,把这个Bean注册到UserPayServiceStrategyFactory中。
以上代码,其实仍是有一些重复代码的,这里面还能够引入模板方法模式进一步精简,这里就不展开了。
还有就是,UserPayServiceStrategyFactory.register调用的时候,第一个参数须要传一个字符串,这里的话其实也能够优化掉。好比使用枚举,或者在每一个策略类中自定义一个getUserType方法,各自实现便可。
本文,咱们经过策略模式、工厂模式以及Spring的InitializingBean,提高了代码的可读性以及可维护性,完全消灭了一坨if-else。
文中的这种作法,你们能够马上尝试起来,这种实践,是咱们平常开发中常常用到的,并且还有不少衍生的用法,也都很是好用。有机会后面再介绍。
其实,若是读者们对策略模式和工厂模式了解的话,文中使用的并非严格意义上面的策略模式和工厂模式。
首先,策略模式中重要的Context角色在这里面是没有的,没有Context,也就没有用到组合的方式,而是使用工厂代替了。
另外,这里面的UserPayServiceStrategyFactory其实只是维护了一个Map,并提供了register和get方法而已,而工厂模式实际上是帮忙建立对象的,这里并无用到。
因此,读者没必要纠结于究竟是不是真的用了策略模式和工厂模式。并且,这里面也再扩展一句,所谓的GOF 23种设计模式,不管从哪本书或者哪一个博客看,都是简单的代码示例,可是咱们平常开发不少都是基于Spring等框架的,根本没办法直接用的。
因此,对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!
若是读者们感兴趣,后续能够出更多的设计模式和Spring等框架结合使用的最佳实践。但愿经过这样的文章,读者能够真正的在代码中使用上设计模式。