深度解析:如何替换掉代码中的ifelse,我女友看完都会了!

平时咱们在写代码时,须要针对不一样状况处理不一样的业务逻辑,用得最多的就是if和else。 可是若是状况太多,就会出现一大堆的“if else”,这就是为何不少遗留系统中,一个函数可能出现上千行的代码。固然你说能够经过抽取方法或者类来实现,每个状况交给一个方法或者对应一个类来处理,可是这样作只是看起来代码整洁了一些,仍是有大量的”if else",后面有新的逻辑时,又要添加更多的“if else",没有从根本上解决问题。java

举个例子,短信发送业务的实现,通常公司会接入多个短信供应商,好比梦网、玄武、阿里云等多个短信平台(咱们称之为短信渠道),可能须要针对不一样的短信类型或者短信平台的稳定性来切换短信渠道:
1.好比阿里云短信管控很严,带营销字样的短信不让发送,则营销类短信须要使用其余短信渠道来发送;
2.也有可能某个短信平台服务挂了暂时不可用,须要切换到另外一个短信渠道;
3.某些短信平台有优惠,则须要临时切换到该短信渠道发送短信;
4.…spring

代码实现

上面的业务场景简单来讲就是:针对不一样的短信渠道来调用对应的短信平台接口实现短信发送。
短信渠道通常配置在文件中,或者配置在数据库中。数据库

代码实现以下(注意下面全部的代码都不能直接运行,只是关键逻辑部分的示例代码):api

烂代码示例

咱们有一个短信发送类:SmsSendService,里面有一个send方法发送短信
SmsSendService.javaide

public class SmsSendService{
    /**
     * @Param phoneNo 手机号
     * @Param content 短信内容
     */
    public void send(String phoneNo,String content){
        //从配置中读取 短信渠道
        String channelType=config.getChannelType();

        //若是是短信渠道A,则调用渠道A的api发送
        if(Objects.equals(channelType,"CHANNEL_A")){
            System.out.println("经过短信渠道A发送短信");
        }
        //若是是短信渠道B,则调用渠道B的api发送
        else if(Objects.equals(channelType,"CHANNEL_B")){
            System.out.println("经过短信渠道B发送短信");
        }
    }
}

若是某天增长了一个短信渠道C,那么接着追加一个”else if…"函数

//... 此处省略部分代码 ...

//从配置中读取 短信渠道
String channelType=config.getChannelType();
//若是是短信渠道A,则调用渠道A的api发送
if(Objects.equals(channelType,"CHANNEL_A")){
    System.out.println("经过短信渠道A发送短信");
}
//若是是短信渠道B,则调用渠道B的api发送
else if(Objects.equals(channelType,"CHANNEL_B")){
    System.out.println("经过短信渠道B发送短信");
}
//ADD: 若是是短信渠道C,则调用渠道C的api发送
else if(Objects.equals(channelType,"CHANNEL_C")){
    System.out.println("经过短信渠道C发送短信");
}

//... 此处省略部分代码 ...

若是又加其余短信渠道了呢?你又写一个“else if …" ?
显然这种作法不可取,也不符合SOLID原则中的”开闭原则“ ——对扩展开放,对更改封闭。
这样咱们每次都须要修改原有代码(对更改没有封闭),不断的添加”if else"。优化

接下来咱们把代码优化一下:ui

优化代码1

定义一个短信渠道的接口 SmsChannelService,全部的短信渠道API都实现该接口;
短信渠道接口 SmsChannelService.java阿里云

public interface SmsChannelService{
    //发送短信
    void send(String phoneNo,String content);
}

短信渠道A SmsChannelServiceImplA.javacode

public class SmsChannelServiceImplA implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道A发送短信");
    }
}

短信渠道B SmsChannelServiceImplB.java

public class SmsChannelServiceImplB implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道B发送短信");
    }
}

经过工厂类来初始化全部短信渠道service
SmsChannelFactory.java

public class SmsChannelFactory {
    private Map<String,SmsChannelService> serviceMap;

    //初始化工厂,将全部的短信渠道Service放入Map中
    public SmsChannelFactory(){
        //渠道类型为 key , 对应的服务类为value :
        serviceMap=new HashMap<String, SmsChannelService>(2);
        serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
        serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
    }

    //根据短信渠道类型得到对应渠道的Service
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

在原来的SmsSendService中调用不一样短信渠道的接口。
原来的 SmsSendService 类优化以下

public class SmsSendService {

    private SmsChannelFactory smsChannelFactory;

    public SmsSendService(){
        smsChannelFactory=new SmsChannelFactory();
    }

    public void send(String phoneNo,String content){
        //从配置中读取 短信渠道
        String channelType=config.getChannelType();
        //获取渠道类型对应的服务类
        SmsChannelService channelService=smsChannelFactory.buildService(channelType);
        //发送短信
        channelService.send(phoneNo,content);
    }

}

这样SmsSendService类很是简洁,把“if else"干掉了,
若是我要增长一个短信渠道C,无需再次更改 SmsSendService 类。
只须要增长一个类 SmsChannelServiceImplC 实现 SmsChannelService 接口,
而后在工厂类 SmsChannelFactory 中增长一行初始化 SmsChannelServiceImplC 的代码便可。

增长短信渠道C的实现 SmsChannelServiceImplC.java

public class SmsChannelServiceImplC implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道C发送短信");
    }
}

修改工厂类 SmsChannelFactory.java

public class SmsChannelFactory {
    private Map<String,SmsChannelService> serviceMap;

    //初始化 serviceMap ,将全部的短信渠道Service放入Map中
    public SmsChannelFactory(){
        //渠道类型为 key , 对应的服务类为value :
        serviceMap=new HashMap<String, SmsChannelService>(3);
        serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
        serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB());
        //ADD 增长一行 SmsChannelServiceImplC 的初始化代码 
        serviceMap.put("CHANNEL_C",new SmsChannelServiceImplC());
    }

    //根据渠道类型构建短信渠道Service
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

“if else"是干掉了,但仍是得修改原来的类 SmsChannelFactory ,不知足"开闭原则",有没有更好得方式呢?

咱们经过使用spring的依赖注入进一步优化代码:

优化代码2

SmsChannelService 接口增长 getChannelType() 方法,这一步很关键。

public interface SmsChannelService {
    //发送短信
    void send(String phoneNo,String content);
    //关键:增长getChannelType()方法,子类实现这个方法用于标识出渠道类型
    String getChannelType();
}

子类增长该方法的实现,并加上 @Service 注解,使其让spring容器管理起来
SmsChannelServiceImplA.java

@Service
public class SmsChannelServiceImplA implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道A发送短信");
    }
    //关键:增长 getChannelType() 实现
    public String getChannelType() {
        return "CHANNEL_A";
    }
}

SmsChannelServiceImplB.java

@Service
public class SmsChannelServiceImplB implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道B发送短信");
    }
    //关键:增长 getChannelType() 实现
    public String getChannelType() {
        return "CHANNEL_B";
    }
}

修改 SmsChannelFactory 类: 这一步也很关键。
SmsChannelFactory.java

@Service
public class SmsChannelFactory {

    private Map<String,SmsChannelService> serviceMap;

    /*注入:经过spring容器将全部实现 SmsChannelService 接口的类的实例注入到 serviceList 中*/
    @Autowired
    private List<SmsChannelService> serviceList;

    /*经过 @PostConstruct 注解,在 SmsChannelFactory 实例化后,来初始化 serviceMap */
    @PostConstruct
    private void init(){
        if(CollectionUtils.isEmpty(serviceList)){
            return ;
        }
        serviceMap=new HashMap<String, SmsChannelService>(serviceList.size());
        //将 serviceList 转换为 serviceMap
        for (SmsChannelService channelService : serviceList) {
            String channelType=channelService.getChannelType();
            //重复性校验,避免不一样实现类的 getChannelType() 方法返回同一个值。
            if(serviceMap.get(channelType)!=null){
                throw new RuntimeException("同一个短信渠道只能有一个实现类");
            }
            /*渠道类型为 key , 对应的服务类为value :
            与“优化代码1”中的经过手工设置“CHANNEL_A"、"CHANNEL_B"相比,
            这种方式更加自动化,后续在增长“CHANNEL_C"无需再改此处代码*/
            serviceMap.put(channelType,channelService);
        }
    }

    //根据渠道类型获取对应短信渠道的Service
    public SmsChannelService buildService(String channelType){
        return serviceMap.get(channelType);
    }
}

SmsSendService 加上 @Service 注解。经过 @Autowired 注入 SmsChannelFactory
SmsSendService.java

@Service
public class SmsSendService {

    @Autowired
    private SmsChannelFactory smsChannelFactory;

    public void send(String phoneNo,String content){
        //从配置中读取短信渠道类型
        String channelType=config.getChannelType();
        //构建渠道类型对应的服务类
        SmsChannelService channelService=smsChannelFactory.buildService(channelType);
        //发送短信
        channelService.send(phoneNo,content);
    }

}

这时,若是须要添加一个渠道C,那真的只须要添加一个 SmsChannelServiceImplC 便可,不再用改原有代码,彻底遵循“开闭原则”。

SmsChannelServiceImplC.java

@Service
public class SmsChannelServiceImplC implements SmsChannelService {
    public void send(String phoneNo, String content) {
        System.out.println("经过短信渠道C发送短信");
    }

    public String getChannelType() {
        return "CHANNEL_C";
    }
}

总结
经过上述优化很好的去掉了 “if else" ,不再会出现”又臭又长“像”卫生卷纸"同样的代码了,并且彻底遵循”开闭原则"。
spring是个好东西,关键看你怎么用。

看完有什么不懂的欢迎在下方留言评论,记得点个赞哦!

相关文章
相关标签/搜索