背景
对于业务开发来说,随着业务的发展,需求只会越来越复杂,为了考虑到各种情况,若没有做相应处理,代码里面不可避免的会出现很多 if..else,在我看来,其中有很多对 if..else 的处理是 隔离 开来做,会更加简洁明了,清晰易懂,方便维护的。
另外,一旦代码中 if..else 过多,就会大大影响其可读性和可维护性,如图:
可读性: 一旦 if..else 长到一定程度,会让人很难理解到底是什么意思,很难让人一连串的去理解具体业务是什么,尤其是没有注释的代码。
可维护性: 因为 if..else 特别多而导致要新增一条分支的时候,就会很难新增,非常容易影响到原有的分支逻辑,不易于维护。
那么这里,我们应该通过什么方法来解决 if..else 过深/过长 的问题呢?
if..else
假设我现在有一个消息处理平台,需求是:
- 接收不同类型的消息,做不同的业务处理
我们接收一个这样的消息类型:
/**
* @author lrfffffff
* 交通事件类型及消息内容
*/
data class TrafficEventMsg(
val msg: String,
val eventType: String
)
对不用的消息类型做不同的处理:
/**
* @author lrfffffff
* if..else 事件处理中心
*/
class IfElseManager {
fun ifElseProc(event: Event) {
if (event.type == "MSG_1") {
println("收到 MSG_1")
println("""解析内容: ${event.msg}""")
println("执行 MSG_1 业务逻辑")
} else if (event.type == "MSG_2") {
println("收到 MSG_2")
println("""解析内容: ${event.msg}""")
println("执行 MSG_2 业务逻辑")
} else if (event.type == "MSG_3") {
println("收到 MSG_3")
println("""解析内容: ${event.msg}""")
println("执行 MSG_3 业务逻辑")
} else if (event.type == "MSG_4") {
println("收到 MSG_4 业务逻辑")
println("""解析内容: ${event.msg}""")
println("执行 MSG_4 业务逻辑")
} else if (event.type == "MSG_5") {
println("收到 MSG_5 业务逻辑")
println("""解析内容: ${event.msg}""")
println("执行 MSG_5 业务逻辑")
} else if (event.type == "MSG_6") {
println("收到 MSG_6 业务逻辑")
println("""解析内容: ${event.msg}""")
println("执行 MSG_6 业务逻辑")
} else if (event.type == "MSG_7") {
println("收到 MSG_7")
println("""解析内容: ${event.msg}""")
println("执行 MSG_7 业务逻辑")
}
// ........
}
}
单看这样的伪代码,是不是已经有种头皮发麻的感觉了?更何光将对应的业务逻辑实现加上,那复杂度可想而知。
那么应该如何解决这种问题?
策略模式
下面我们尝试用比较简单的 策略模式 来提升代码的可维护性和可读性。
首先定义一个策略类接口:
/**
* @author 30005665
*/
interface EventStrategy {
/**
* 处理事件类型
*/
fun procEvent(event: Event)
}
然后定义几个策略类:
/**
* @author lrfffffff
*/
class MSG1ProcService : EventStrategy {
override fun procEvent(event: Event) {
if ("MSG_1" == event.type) {
println("收到 MSG_1")
println("""解析内容: ${event.msg}""")
println("执行 MSG_1 业务逻辑")
}
}
}
class MSG2ProcService : EventStrategy {
override fun procEvent(event: Event) {
if ("MSG_2" == event.type) {
println("收到 MSG_2")
println("""解析内容: ${event.msg}""")
println("执行 MSG_2 业务逻辑")
}
}
}
class MSG3ProcService : EventStrategy {
override fun procEvent(event: Event) {
if ("MSG_3" == event.type) {
println("收到 MSG_3")
println("""解析内容: ${event.msg}""")
println("执行 MSG_3 业务逻辑")
}
}
}
// ...
引入了策略之后,就可以按照如下方式对不同的事件做处理:
/**
* @author 30005665
*/
fun main() {
var strategy = MSG1ProcService()
var event = Event("MSG_1", "我是 MSG_1")
strategy.procEvent(event)
strategy = MSG1ProcService()
event = Event("MSG_2", "我是 MSG_2")
strategy.procEvent(event)
}
以上就是一个简单的例子,可以在代码中初始化不同事件的策略类,然后执行对应策略的处理方法。
但是真正在代码的场景中,比如 Web 项目中使用,这个 Demo 根本没办法直接使用的。主要原因有这两点:
- 在 Web 项目中我们创建出来的策略类都是被 Spring 托管的,我们不会自己去 New 一个实例出来。
- 在 Web 项目中,如果真要处理事件,也要先知道事件类型,比如说从 数据库、接口 中获取事件类型,然后根据类型获取不同的策略类执行对应的事件处理方法。
那么 Web 中真正事件处理的话,应该是这样的:
/**
* @author lrfffffff
* 事件处理方法
*/
fun procEvent(event: Event) {
val type = event.type
if ("MSG_1" == type) {
val msG1ProcService = SpringContextHelper.getBean(MSG1ProcService::class)
msG1ProcService.procEvent(event)
}
if ("MSG_2" == type) {
val msG2ProcService = SpringContextHelper.getBean(MSG2ProcService::class)
msG2ProcService.procEvent(event)
}
if ("MSG_3" == type) {
val msG3ProcService = SpringContextHelper.getBean(MSG3ProcService::class)
msG3ProcService.procEvent(event)
}
}
这里我们发现,虽然代码可读性和可维护性是好了一些,但是并没有去掉 if..else ,这里我们可以看到,要用到对应的策略类,还是 需要对类型的判断才可以获取到对应的bean 。
如何才可以消除这些判断呢?
工厂模式
为了方便我们从 Spring 中获取 EventStrategy 的各个策略类,我们创建一个工厂类:
/**
* @author lrfffffff
* 事件处理策略工厂类
*/
class EventStrategyFactory {
companion object {
private val events = HashMap<String, EventStrategy>()
fun getByEventType(eventType: String): EventStrategy? {
return events.get(eventType)
}
fun register(eventType: String, eventStrategy: EventStrategy) {
events.put(eventType, eventStrategy)
}
}
}
这个 EventStrategyFactory 中定义了一个 HashMap, 用来保存所有的策略类的实例,并提供一个 GetByEventType 的方法,可以直接根据类型获取对应的类的实例。
这样的话,我们的事件处理的代码就可以大大的优化:
/**
* @author lrfffffff
* 事件处理方法
*/
fun procEvent(event: Event) {
val type = event.type
val eventStrategy = EventStrategyFactory.getByEventType(type)
?: throw Exception("type valid")
eventStrategy.procEvent(event)
}
我们直接通过自定义工厂的 getByEventType() 方法就可以获取到对应的策略类了。
这里通过 策略+工厂 ,代码很大程度优化了,再一步提升可读性和可维护性。
那么策略类是如何注册进工厂的呢?工厂里面的 Map 是如何初始化的?
Spring Bean的注册
细心的伙伴相信都看到我工厂类 EventStrategyFactory 里面的 register() 方法了,这就是用来注册策略类的。
下面我们只需要考虑如何在服务初始化的时候,将对应的策略类注册到 工厂类 EventStrategyFactory 中即可。
这里我们可以借用 Spring 提供的 InitializingBean 接口,这个接口为 Bean 提供了属性初始化后的处理方法 afterPropertiesSet() ,凡是继承 InitializingBean 接口的类,在 Bean 的属性初始化后都会执行 afterPropertiesSet() 方法。
那么我们的策略类可以这么写:
/**
* @author lrfffffff
*/
@Service
class MSG1ProcService : EventStrategy, InitializingBean {
companion object {
const val MSG = "MSG_1"
}
override fun afterPropertiesSet() {
EventStrategyFactory.register(MSG, this)
}
override fun procEvent(event: Event) {
if ("MSG_1" == event.type) {
println("收到 MSG_1")
println("""解析内容: ${event.msg}""")
println("执行 MSG_1 业务逻辑")
}
}
}
@Service
class MSG2ProcService : EventStrategy, InitializingBean {
companion object {
const val MSG = "MSG_2"
}
override fun afterPropertiesSet() {
EventStrategyFactory.register(MSG, this)
}
override fun procEvent(event: Event) {
if ("MSG_2" == event.type) {
println("收到 MSG_2")
println("""解析内容: ${event.msg}""")
println("执行 MSG_2 业务逻辑")
}
}
}
@Service
class MSG3ProcService : EventStrategy, InitializingBean {
companion object {
const val MSG = "MSG_3"
}
override fun afterPropertiesSet() {
EventStrategyFactory.register(MSG, this)
}
override fun procEvent(event: Event) {
if ("MSG_3" == event.type) {
println("收到 MSG_3")
println("""解析内容: ${event.msg}""")
println("执行 MSG_3 业务逻辑")
}
}
}
只需要每一个策略服务的实现类都实现 InitializingBean 接口,并实现其 afterPropertiesSet() 方法,在这个方法中调用 EventStrategyFactory.register() 即可。
这样,在 Spring 初始化的时候,当创建 MSG1ProcService、MSG2ProcService和 MSG3ProcService 的时候,会在 Bean 的属性初始化之后,把这个 Bean 注册到 EventStrategyFactory 中。
以上针对 策略+工厂 的模式消除 if..else 还有很多可以优化的地方,存在一些重复代码,这里就不往下深入了。
当然如果你对 策略模式和工厂模式 了解的话,文中使用的并不是严格意义上面的策略模式和工厂模式。
首先,策略模式中重要的 Context 角色在这里面是没有的,没有 Context,也就没有用到组合的方式,而是使用工厂代替了。
另外,这里面的 EventStrategyFactory 其实只是维护了一个 Map,并提供了 Register 和 Get 方法而已,而工厂模式其实是帮忙创建对象的,这里并没有用到。
所以,不必纠结于到底是不是真的用了策略模式和工厂模式。对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!希望我们通过这里的写法,能真正的在代码中使用上设计模式。