Spring 与策略模式双剑合壁,让你彻底的消灭冗余的if

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

实际业务场景

最近在项目中需要开发消息发送平台,需要根据 type 值判断来进行相应的业务处理以及消息发送方式,这样就涉及到重复的 if-else 的问题,为了解决这样重复的代码来造成的代码冗余的问题,所以考虑使用策略模式,根据具体的场景执行对应的策略,来解决问题。本文简单写一个测试 Demo,实际开发需要根据具体业务,具体实现对应的业务逻辑。

具体实现

当我们遇到这样的逻辑处理时,第一反应是针对 type 进行 if…else… 或者是 switch 的逻辑判断,从而区分不同业务逻辑处理。

基于 if…else… 的伪代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public int sendMessage(int type, Message message) {
if (type == 1) {
// 省略业务处理
return mail.sendMessage(message); // 进行邮件发送
} else if (type == 2) {
// 省略业务处理
return mobile.sendMessage(message); // 进行手机短信发送
} else if (type == 3) {
// 省略业务处理
return app.sendSendMessage(message); // 进行 app 消息推送
} else if (type == 4){
// ...
}
//...
}

假设上面的代码是用来对消息进行多渠道的发送,根据不同 type 来进行发送方式的选择。当然真实的业务场景不可能是这么简单的判断。

首先对照一下设计模式的开闭原则:面对扩展开放,面对修改关闭。

上述代码,如果某个发送方式改变了,那么这段代码就要进行修改,或者如果新增了一个发送方式,这段代码同样需要修改。一旦修改必然会影响到其他方式的业务逻辑。完全不符合开闭原则,同时代码中还充斥着大量的 if…else…,如果业务复杂,代码会急速膨胀。

那么,下面我们就针对以上实例,用策略模式来进行重新设计。

基于策略模式的伪代码

首先定义一个发送消息的接口 ISendMessageStrategy。实战过程中可根据具体情况采用接口或抽象类。

1
2
3
4
5
6
7
8
9
java复制代码public interface ISendMessageStrategy {

/**
* 发送消息
*
* @param message 发送的消息内容
*/
void sendMessage(Message message);
}

在接口中提供一个方法,也就是发送消息的方法。这里因为是接口,所以定义的方法就需要子类必须实现。下面便是针对此接口的具体实现,不同的发送方式有不同的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码/**
* 邮件发送的实现
**/
@Slf4j
public class EmailSendMessageStrategy implemets ISendMessageStrategy {
@Override
public void sendMessage(Message message) {
// 省略业务处理
mail.sendMessage(message);
}
}

/**
* 短信发送的实现
**/
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
@Override
public void sendMessage(Message message) {
// 省略业务处理
phone.sendMessage(message);
}
}

我们来实现一个持有接口 ISendMessageStrategy 的角色类 SendMessageHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class SendMessageHandler {

/**
* 持有策略抽象类
*/
private ISendMessageStrategy sendMessageStrategy;

// 通过构造方法注入,也可以通过其他方式注入
public SendMessageHandler(ISendMessageStrategy sendMessageStrategy) {
this.sendMessageStrategy = sendMessageStrategy;
}

public void sendMessage(Message message) {
return sendMessageStrategy.sendMessage(message);
}

}

最后,我们来看一下如何调用该策略类

1
2
3
4
5
6
java复制代码public class Test {
public static void main(String[] args) {
SendMessageHandler handler = new SendMessageHandler(new MobileSendMessageStrategy());
handler.sendMessage(new Message());
}
}

使用 Spring 来管理对象

上面的改进已经避免了大量的 if… else…,此时如果项目使用的是 Spring 项目,我们再进一步改进, 此时主要利用 Spring 的 @Autowired 注解来将实例化的策略实现类注入到一个 Map 当中,然后通过 key 可以方便的拿到服务。

首先将策略实现类通过@Service 注解进行实例化,并指定实例化的名称。以短信发送的实现为例:

1
2
3
4
kotlin复制代码@Component("mobileSendMessageStrategy")
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
// ...
}

其他策略实现类与上相同,依次实例化。最后改造环境角色类为 SendMessageHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@Component
public class SendMessageHandler implements ApplicationContextAware {

private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();

public ISendMessageStrategy get(String beanName) {
return strategyMap.get(beanName);
}

@Override
public void setApplicationContext(ApplicationContext context) throws Exception {
strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class);
}
}

applicationContext.getBeansOfType(ISendMessageStrategy.class) 会将容器中 ISendMessageStrategy 的实现类(注解了 @Component)放到该 map 中。其中 key 就是 @Component 中指定的实例化服务的名称,value 便是对应的对象。

1
2
3
4
5
6
7
8
java复制代码public class Test {
@Resource
private SendMessageHandler messageHandler;

public void test() {
messageHandler.get("mobileSendMessageStrategy").sendMessage(new Message());
}
}

借助枚举类进一步优化

我们再进一步改进。我们不再在策略角色类中调用策略类的方法了,只让策略角色类作为工厂的角色,返回对应的服务。而相关服务方法的调用由客户端直接调用实现类的方法。

同时,针对服务的名称和类型我们通过枚举进行映射。先来定义一个枚举类:

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
35
36
37
38
39
java复制代码public enum TypeEnum {

MAIL(0, "mailSendMessageStrategy", "邮件"),
MOBILE(1, "mobileSendMessageStrategy", "短信"),
APP(2, "appSendMessageStrategy", "app");

TypeEnum(int type, String serviceName, String desc) {
this.type = type;
this.serviceName = serviceName;
this.desc = desc;
}

public static TypeEnum valueOf(int type) {
for (TypeEnum typeEnum : TypeEnum.values()) {
if (typeEnum.getType() == type) {
return typeEnum;
}
}
return null;
}

private int type;

private String serviceName;

private String desc;

public int getType() {
return type;
}

public String getServiceName() {
return serviceName;
}

public String getDesc() {
return desc;
}
}

然后改造环境角色类为 SendMessageHandler:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Component
public class SendMessageHandler implements ApplicationContextAware {

private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();

public ISendMessageStrategy get(int type) {
TypeEnum typeEnum = TypeEnum.valueOf(type);
return strategyMap.get(typeEnum.getServiceName());
}

@Override
public void setApplicationContext(ApplicationContext context) throws Exception {
strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class);
}
}

测试一下

1
2
3
4
5
6
7
8
9
java复制代码public class Test {
@Resource
private SendMessageHandler messageHandler;

public void test() {
int type = 1;
messageHandler.get(type).sendMessage(new Message());
}
}

此时,如果新添加算法,只用创建对应算法的服务,然后在枚举类中映射一下关系,便可在不影响客户端调用的情况进行扩展。当然,根据具体的业务场景还可以进行进一步的改造。

总结

设计模式可以可以更好的扩展代码,但是一定程度上也加大了代码的阅读成本。所以在实际的项目中,不要一味的追求设计模式,要结合实际的业务情况进行合理的选择。当判断项固定且较少时,if…else… 也是一种更高效且便于维护的方式。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%