随着项目的越来越复杂,条件分支越来越多,代码充斥着大量的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);
}
从这段可以看到,函数需要对传入进行校验,这里是一个分支,根据类型选择推送类型,这里有三个分支,如果之后有新的渠道,那我们就又需要修改这里的代码,增加判断分支,导致函数越来越复杂…
所以随着项目越来越复杂,后续有重构的时候要多考虑如何消除这里判断分支,当然,不是所有的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() ...
}
调用测试:
@Test
public void test_enum() {
boolean push = PushChannel.valueOf("sms").getPushService().push("Hello.");
Assert.assertEquals(true, push);
}
工厂模式
接口。
public interface PushService {
boolean push(String msg);
}
这里的枚举类就不干那么多活了。
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...
工厂类,对于一些空指针判断还可以借助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());
}
}
单元测试跑看看。
@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接口和枚举类型。
public interface PushService {
PushChannel type();
boolean push(String msg);
}
public enum PushChannel {
sms,
email,
wechat;
}
各实现类。
@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...
可以用列表或者哈希表的方式注入,两种选一种就行了
@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);
}
}
使用方式。
PushServiceInterfaceContext pushServiceInterfaceContext;
@Autowired
BizController(PushServiceInterfaceContext pushServiceInterfaceContext) {
this.pushServiceInterfaceContext = pushServiceInterfaceContext;
}
//...
//调用
pushServiceInterfaceContext.apply(type, msg);
写完了代码还是稍微有点多,在两三个分支的情况下其实用不上这里模式,搞得更复杂了。简单的东西就不用设计过度,过早优化,复杂的业务也要权衡一下,如何是经常变动修改,持续维护的情况那就值了。