随着项目的越来越复杂,条件分支越来越多,代码充斥着大量的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;
}
};
}
// public static PushService emailPush() ...
// public static PushService wechatPush() ...
}

调用测试:

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;
}
}
// EmailPushService...
// WeChatPushService...

工厂类,对于一些空指针判断还可以借助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;
}
// 内部有空指针判断可借助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
@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;
}
}
// 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
29
30
31
32
33
34
@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
PushServiceInterfaceContext pushServiceInterfaceContext;
@Autowired
BizController(PushServiceInterfaceContext pushServiceInterfaceContext) {
this.pushServiceInterfaceContext = pushServiceInterfaceContext;
}
//...
//调用
pushServiceInterfaceContext.apply(type, msg);

源码地址。

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