【java】if..else 代码消除

436 阅读6分钟

背景

对于业务开发来说,随着业务的发展,需求只会越来越复杂,为了考虑到各种情况,若没有做相应处理,代码里面不可避免的会出现很多 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 根本没办法直接使用的。主要原因有这两点:

  1. 在 Web 项目中我们创建出来的策略类都是被 Spring 托管的,我们不会自己去 New 一个实例出来。
  2. 在 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 方法而已,而工厂模式其实是帮忙创建对象的,这里并没有用到。

所以,不必纠结于到底是不是真的用了策略模式和工厂模式。对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!希望我们通过这里的写法,能真正的在代码中使用上设计模式。

引用:mp.weixin.qq.com/s/pSqyGcAb8…