• 几种消除if/else的方式


    随着项目的越来越复杂,条件分支越来越多,代码充斥着大量的if/else和switch/case判断,甚至是多层嵌套的if/else,我们需要重新重构或者组织逻辑代码。

    先看随手写的一个根据渠道类型推送消息例子。

    public boolean pushByType(String type, String msg) {
        if (type == null) return false;
        if ("sms".equals(type)) return smsPushService.push(msg);
        else if ("email".equals(type)) return emailPushService.push(msg);
        else return wechatPushService.push(msg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    从这段可以看到,函数需要对传入进行校验,这里是一个分支,根据类型选择推送类型,这里有三个分支,如果之后有新的渠道,那我们就又需要修改这里的代码,增加判断分支,导致函数越来越复杂…

    所以随着项目越来越复杂,后续有重构的时候要多考虑如何消除这里判断分支,当然,不是所有的if/else都要消除,比如上面的null值判断。总结:

    • 对于null或boolean判断,大多时候不需要管
    • 对于变量的状态有多种,并且将来还会增加的情况,就需要做优化
    • 对于对于if/else中再嵌套if/else的情况,可以逆向逻辑判断,将内部的逻辑提取到同一级后方
    • 使用switch/case一般都意味着多种状态,不然你也用不着是吧

    消除if/else有多种方法,设计模式中的工厂模式和策略模式就可以用来处理这个问题,此外利用枚举类的特性也可以,下面具体看看这几种方法。

    枚举

    在枚举类的基础上扩展,将业务实现类通过枚举类构造函数传进来,这样就把类型和对应的处理方法关联起来。坏处是枚举类变得臃肿起来,通常我们只是用枚举类来定义一组常量。

    public enum PushChannel {
        sms(PushService.smsPush()),
        email(PushService.emailPush()),
        we_chat(PushService.wechatPush());
    
        PushService pushService;
    
        private PushChannel(PushService pushService) {
            this.pushService = pushService;
        }
    
        public PushService getPushService() {
            return pushService;
        }
    }
    
    public interface PushService {
        public boolean push(String msg);
    
        public static PushService smsPush() {
            return new PushService() {
                public boolean push(String msg) {
                    System.out.println("sms:" + msg);
                    return true;
                }
            };
        }
        // public static PushService emailPush() ...
        // public static PushService wechatPush() ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    调用测试:

    @Test
    public void test_enum() {
        boolean push = PushChannel.valueOf("sms").getPushService().push("Hello.");
        Assert.assertEquals(true, push);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    工厂模式

    接口。

    public interface PushService {
        boolean push(String msg);
    }
    
    • 1
    • 2
    • 3

    这里的枚举类就不干那么多活了。

    enum PushChannel {
        sms("sms"),
        email("email"),
        wechat("wechat");
    
        String channel;
        PushChannel(String channel) {
            this.channel = channel;
        }
    
        String getVal() {
            return channel;
        }
    }
    
    /**
     * 推送渠道有短信、邮件、微信等等......
     * 懒得新建类文件所以直接写在这里一起
     */
    class SmsPushService implements PushService {
        @Override
        public boolean push(String msg) {
            System.out.println("sms:" + msg);
            return true;
        }
    }
    // EmailPushService...
    // WeChatPushService...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    工厂类,对于一些空指针判断还可以借助Optional类来消除。

    public class PushFactory {
        private static Map<String, PushService> pushMap = new HashMap<>();
    
        static {
            pushMap.put(PushChannel.sms.getVal(), new SmsPushService());
            pushMap.put(PushChannel.email.getVal(), new EmailPushService());
            pushMap.put(PushChannel.wechat.getVal(), new WeChatPushService());
        }
    
        // 常规方法
        public static PushService getPushService(String type) {
            PushService pushService = pushMap.get(type);
            if (pushService == null) throw new NoSuchElementException();
            return pushService;
        }
        // 内部有空指针判断可借助Optional类
        public static PushService getPushServiceOptional(String type) {
            
            return Optional.ofNullable(pushMap.get(type))
                    .orElseThrow(() -> new NoSuchElementException());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    单元测试跑看看。

    @Test
    public void test() {
        boolean sms = PushFactory.getPushService("sms").push("sms msg.");
        Assert.assertEquals(true, sms);
    
        boolean email = PushFactory.getPushService("email").push("email msg.");
        Assert.assertEquals(true, email);
    
        Assert.assertThrows(NoSuchElementException.class, () -> PushFactory.getPushService("gms"));
    }
    
    @Test
    public void test_optional() {
        boolean email = PushFactory.getPushServiceOptional("email").push("email msg.");
        Assert.assertEquals(true, email);
    
        Assert.assertThrows(NoSuchElementException.class, () -> PushFactory.getPushServiceOptional("gms"));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    策略模式

    策略的实现跟工厂很像,只不过工厂是生产对象再自己执行函数,策略则是行为型模式,直接执行行为。

    这里顺便结合下Spring看看是如何使用的。

    Strategy接口和枚举类型。

    public interface PushService {
        PushChannel type();
        boolean push(String msg);
    }
    public enum PushChannel {
        sms,
        email,
        wechat;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    各实现类。

    @Service
    public class SmsPushService implements PushService {
        @Override
        public boolean push(String msg) {
            System.out.println("sms:" + msg);
            return true;
        }
    
        @Override
        public PushChannel type() {
            return PushChannel.sms;
        }
    }
    // EmailPushService...
    // WeChatPushService...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    可以用列表或者哈希表的方式注入,两种选一种就行了

    @Component
    public class PushServiceInterfaceContext {
    
        private final List<PushService> serviceList;
    
        // 提示 field injection is not recommended,所以用构造注入
        public PushServiceInterfaceContext(List<PushService> serviceList) {
            this.serviceList = serviceList;
        }
    
        public boolean apply(String type, String msg) {
            Optional<PushService> pushService = serviceList.stream()
                    .filter(service -> service.type() == PushChannel.valueOf(type))
                    .findAny();
            PushService service = pushService.orElseThrow(() -> new NoSuchElementException());
            return service.push(msg);
        }
    }
    
    
    @Component
    public class PushServiceMapContext {
        private final Map<String, PushService> serviceMap = new ConcurrentHashMap<>();
    
        public PushServiceMapContext(Map<String, PushService> serviceMap) {
            this.serviceMap.clear();
            this.serviceMap.putAll(serviceMap);
        }
    
        public boolean apply(String type, String msg) {
            PushService service = serviceMap.get(type + "PushService");
            return service.push(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    使用方式。

    PushServiceInterfaceContext pushServiceInterfaceContext;
    @Autowired
    BizController(PushServiceInterfaceContext pushServiceInterfaceContext) {
        this.pushServiceInterfaceContext = pushServiceInterfaceContext;
    }
    //...
    //调用
    pushServiceInterfaceContext.apply(type, msg);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    源码地址。

    写完了代码还是稍微有点多,在两三个分支的情况下其实用不上这里模式,搞得更复杂了。简单的东西就不用设计过度,过早优化,复杂的业务也要权衡一下,如何是经常变动修改,持续维护的情况那就值了。


    原文链接。访问我的个人网站查看更多文章。

  • 相关阅读:
    Ubuntu 常用命令
    linux下的自旋锁、信号量、互斥、完成量
    第28期 | GPTSecurity周报
    【全网最全】springboot整合JSR303参数校验与全局异常处理
    【★★★★★ 第6章 图总结笔记 2022 9.13】
    DDD-如何集成限界上下文和应用服务的使用
    Optimize(程序优化)
    Android SDK安装配置学习
    分享微信使用技巧,快来涨姿势啦
    Self-supervised Low Light Image Enhancement and Denoising 论文阅读笔记
  • 原文地址:https://blog.csdn.net/u012809062/article/details/125498396