随着项目的越来越复杂,条件分支越来越多,代码充斥着大量的if/else和switch/case判断,甚至是多层嵌套的if/else,我们需要重新重构或者组织逻辑代码。
先看随手写的一个根据渠道类型推送消息例子。
1 2 3 4 5 6
| 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); }
|
从这段可以看到,函数需要对传入进行校验,这里是一个分支,根据类型选择推送类型,这里有三个分支,如果之后有新的渠道,那我们就又需要修改这里的代码,增加判断分支,导致函数越来越复杂...
所以随着项目越来越复杂,后续有重构的时候要多考虑如何消除这里判断分支,当然,不是所有的if/else都要消除,比如上面的null值判断。总结:
- 对于null或boolean判断,大多时候不需要管
- 对于变量的状态有多种,并且将来还会增加的情况,就需要做优化
- 对于对于if/else中再嵌套if/else的情况,可以逆向逻辑判断,将内部的逻辑提取到同一级后方
- 使用switch/case一般都意味着多种状态,不然你也用不着是吧
消除if/else有多种方法,设计模式中的工厂模式和策略模式就可以用来处理这个问题,此外利用枚举类的特性也可以,下面具体看看这几种方法。
枚举
在枚举类的基础上扩展,将业务实现类通过枚举类构造函数传进来,这样就把类型和对应的处理方法关联起来。坏处是枚举类变得臃肿起来,通常我们只是用枚举类来定义一组常量。
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
| 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; } }; } }
|
调用测试:
1 2 3 4 5
| @Test public void test_enum() { boolean push = PushChannel.valueOf("sms").getPushService().push("Hello."); Assert.assertEquals(true, push); }
|
工厂模式
接口。
1 2 3
| public interface PushService { boolean push(String 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
| 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; } }
|
工厂类,对于一些空指针判断还可以借助Optional类来消除。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; } 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
| @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")); }
|
策略模式
策略的实现跟工厂很像,只不过工厂是生产对象再自己执行函数,策略则是行为型模式,直接执行行为。
这里顺便结合下Spring看看是如何使用的。
Strategy接口和枚举类型。
1 2 3 4 5 6 7 8 9
| public interface PushService { PushChannel type(); boolean push(String msg); } public enum PushChannel { sms, email, wechat; }
|
各实现类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @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; } }
|
可以用列表或者哈希表的方式注入,两种选一种就行了
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
| @Component public class PushServiceInterfaceContext {
private final List<PushService> serviceList;
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
| PushServiceInterfaceContext pushServiceInterfaceContext; @Autowired BizController(PushServiceInterfaceContext pushServiceInterfaceContext) { this.pushServiceInterfaceContext = pushServiceInterfaceContext; }
pushServiceInterfaceContext.apply(type, msg);
|
源码地址。
写完了代码还是稍微有点多,在两三个分支的情况下其实用不上这里模式,搞得更复杂了。简单的东西就不用设计过度,过早优化,复杂的业务也要权衡一下,如何是经常变动修改,持续维护的情况那就值了。