优雅的Kotin事件处理 - 重构

通过这些专业的重构技巧, 释放 Kotlin 代码的峰值性能.

在软件开发的世界里, 代码重构是将我们从纠结而低效的代码中解救出来的英雄. 在本文中, 我们将开始一场冒险, 用以重构处理各种事件的 Kotlin 代码. 我们的任务是什么? 提高性能和风格, 使代码更流畅, 更易维护, 并让我们的工作充满乐趣.

我们的目标

在改造 Kotlin 事件处理的过程中, 我们的目标是完善代码, 使其更高效, 更易读, 更易维护. 我们将引入各种改进, 包括:

  • HashMap代替复杂的when语句, 以获得快如闪电的(O(1))性能.
  • 使用内联函数和reified类型参数来优化语法.
  • 采用委托属性, 实现更简洁的依赖注入.
  • 通过启用多个专门的事件处理函数, 坚持单一责任原则.

第 1 步: 起跑线

我们的冒险之旅从查看原始代码开始. 该代码库通过名为handleBlockEvent的函数和名为onEvent的事件处理函数来管理各种块事件. 让我们揭开原始代码的神秘面纱:

open fun onEvent(event: Event) {    
    // ...
    handleBlockEvent(engine, getBlockForEvents(), checkNotNull(assetsRepo.fontFamilies.value).getOrThrow())
}

fun handleBlockEvent(engine: Engine, block: DesignBlock, fontFamilyMap: Map<String, FontFamilyData>, event: BlockEvent) {
    when (event) {
        BlockEvent.OnDelete -> engine.delete(block)
        BlockEvent.OnBackward -> engine.sendBackward(block)
        BlockEvent.OnDuplicate -> engine.duplicate(block)
        BlockEvent.OnForward -> engine.bringForward(block)
        BlockEvent.ToBack -> engine.sendToBack(block)
        BlockEvent.ToFront -> engine.bringToFront(block)
        BlockEvent.OnChangeFinish -> engine.editor.addUndoStep()
        is BlockEvent.OnChangeBlendMode -> onChangeBlendMode(engine, block, event.blendMode)
        is BlockEvent.OnChangeOpacity -> engine.block.setOpacity(block, event.opacity)
        is BlockEvent.OnChangeFillColor -> onChangeFillColor(engine, block, event.color)
        // and so on...
    }
}

sealed class BlockEvent : Event {
    object OnChangeFinish : BlockEvent
    object OnForward : BlockEvent
    object OnBackward : BlockEvent
    object OnDuplicate : BlockEvent
    object OnDelete : BlockEvent
    object ToFront : BlockEvent
    object ToBack : BlockEvent
    data class OnChangeBlendMode(val blendMode: BlendMode) : BlockEvent
    data class OnChangeOpacity(val opacity: Float) : BlockEvent
    data class OnChangeFillColor(val color: Color) : BlockEvent
    // and so on...
}

要使用原始代码, 通常需要使用特定事件来调用onEvent函数:

onEvent(BlockEvent.OnChangeFillColor(Color.RED))

这将触发handleBlockEvent函数来处理手头的事件. 现在, 让我们开始第一次重构冒险.

第 2 步: 揭开HashMap和Payload的神秘面纱, 实现峰值性能

在第一次重构中, 我们引入了值得信赖的HashMap来将每个事件类型映射到其对应的操作. 这一英勇之举消除了对复杂的when语句的需求, 使我们的代码更加高效. 我们还公布了一种Payload机制, 用于向事件处理程序传递重要数据.

请看重构后的代码:

abstract class EventsHandler<Payloads>(
    val fillPayload: (cache: Payloads) -> Unit
) {
    abstract val payloadCache: Payloads
    private val eventMap = mutableMapOf<KClass<out Event>, Payloads.(event: Event) -> Unit>()

    fun handleEvent(event: Event) {
        eventMap[event::class]?.let {
            it.invoke(payloadCache.also { fillPayload(it) }, event)
        }
    }

    operator fun <EventType : Event> set(event: KClass<out EventType>, lambda: Payloads.(event: EventType) -> Unit) {
        eventMap[event] = lambda as Payloads.(event: Event) -> Unit
    }
}

class BlockEventsHandler(fillPayload: (cache: BlockEventsHandler.Payloads) -> Unit) : EventsHandler<BlockEventsHandler.Payloads>(fillPayload) {
    class Payloads {
        lateinit var engine: Engine
        lateinit var block: DesignBlock
        lateinit var fontFamilyMap: Map<String, FontFamilyData>
    }
    override val payloadCache: Payloads = Payloads()

    init {
        it[BlockEvent.OnDelete::class] = { engine.delete(block) }
        it[BlockEvent.OnBackward::class] = { engine.sendBackward(block) }
        it[BlockEvent.OnDuplicate::class] = { engine.duplicate(block) }
        it[BlockEvent.OnForward::class] = { engine.bringForward(block) }
        it[BlockEvent.ToBack::class] = { engine.sendToBack(block) }
        it[BlockEvent.ToFront::class] = { engine.bringToFront(block) }
        it[BlockEvent.OnChangeFinish::class] = { engine.editor.addUndoStep() }
        it[BlockEvent.OnChangeBlendMode::class] = { onChangeBlendMode(engine, block, it.blendMode) }
        it[BlockEvent.OnChangeOpacity::class] = { engine.block.setOpacity(block, it.opacity) }
        it[BlockEvent.OnChangeFillColor::class] = { onChangeFillColor(engine, block, it.color) }
        // and so on...
    }
}

private val blockEventHandler = BlockEventsHandler {
    it.engine = engine
    it.block = getBlockForEvents()
    it.fontFamilyMap = checkNotNull(assetsRepo.fontFamilies.value).getOrThrow()
}

open fun onEvent(event: Event) {    
    // ...
    blockEventHandler.handleEvent(event)
}

性能提升

通过利用HashMap的强大功能, 我们加快了事件处理的速度. 现在, 处理事件的时间复杂度仅为闪电般的(O(1)), 与繁琐的when语句的(O(n))时间复杂度相比, 这是一个巨大的进步. 我们的Payload机制增加了语法糖. 它使我们能够将所有必要的数据捆绑到一个对象中, 从而使我们的代码更易读, 更易维护.

💡 注意: 使用HashMap代替大型when语句可显著提高性能. 它可以将速度提高 40 到 150 倍. 不过, 解释细节将超出本文的范围. 因此, 我将在今后的博文中介绍它以及其他 Kotlin 性能难题.

重构后的代码仍然和以前一样简单:

onEvent(BlockEvent.OnChangeFillColor(Color.RED))

这仍然会触发BlockEventsHandler中的handleEvent方法, 然后根据事件类型执行相应的操作. BlockEvent本身是一个包含所有事件细节的数据对象, 它可以作为 lambda 参数.

关于Payload的说明

Payload创建是一个动态 lambda 函数, 每次处理事件时都会执行. 这样可以确保所有不属于事件的变量都是最新的. 鉴于我们处理的是每个事件处理程序的单线程, 缓存Payload是完全安全的.

第 3 步: 使用 Infix 函数增加语法糖

在下一步中, 我们将把语法的表现力和可读性提升到一个新的水平. 我们引入了一个名为to的infix函数, 让我们可以优雅地将事件类映射到相应的动作.

请看更新后的代码:

abstract class EventsHandler<Payloads>(
    val fillPayload: (cache: Payloads) -> Unit
) {
    infix fun <Payloads, EventType : Event> KClass<out EventType>.to(lambda: Payloads.(event: EventType) -> Unit) {
        eventMap[event] = lambda as Payloads.(event: Event) -> Unit
    }
    // ... (rest of the code remains the same)
}

class BlockEventsHandler(
    manager: EventsManager,
    override val fillPayload: (cache: TextBlockEventsHandler) -> Unit
) : EventsHandler<TextBlockEventsHandler>(manager) {
    lateinit var engine: Engine
    lateinit var block: DesignBlock
    lateinit var fontFamilyMap: Map<String, FontFamilyData>

    init {
        BlockEvent.OnDelete::class to { 
            engine.delete(block) 
        }
        BlockEvent.OnBackward::class to { 
            engine.sendBackward(block) 
        }
        BlockEvent.OnDuplicate::class to { 
            engine.duplicate(block) 
        }
        BlockEvent.OnForward::class to { 
            engine.bringForward(block) 
        }
        BlockEvent.ToBack::class to { 
            engine.sendToBack(block) 
        }
        BlockEvent.ToFront::class to { 
            engine.bringToFront(block) 
        }
        BlockEvent.OnChangeFinish::class to { 
            engine.editor.addUndoStep() 
        }
        BlockEvent.OnChangeBlendMode::class to { 
            onChangeBlendMode(engine, block, it.blendMode) 
        }
        BlockEvent.OnChangeOpacity::class to { 
            engine.block.setOpacity(block, it.opacity) 
        }
        BlockEvent.OnChangeFillColor::class to { 
            onChangeFillColor(engine, block, it.color) 
        }
        // ...
    }
}

语法优美和性能

infix函数to的引入为语法增添了一抹亮色, 增强了代码的表现力, 使使用更加自然. 这使得每个事件的内容一目了然. 不用担心, 得益于我们值得信赖的HashMap, 性能仍然保持在极快的(O(1)).

语法的灵活性

虽然这里使用了to关键字, 但你也可以用其他术语来代替它, 如handle,trigger或任何最适合你的上下文的术语. 灵活性就是本游戏的名字.

第 4 步: 使用内联函数以保持优雅

然而, 这仍然不够完美, 因为::class破坏了阅读的流畅性.

因此, 让我们换一种方式. 让我们尝试引入一种更优雅的方法来注册事件. 让我们不再需要每次注册事件处理程序时都指定::class, 这将使我们的代码更加简洁易读.

这可以通过一个内联函数来实现, 该函数带有一个经过验证的类型参数, 可在运行时维护类引用.

为此, 我们用这个新的register函数扩展了EventsHandler类:

class EventsHandler(
    register: EventsHandler.() -> Unit,
) {
    inline fun <reified EventType : BaseEvent> register(noinline lambda: (event: EventType) -> Unit) : Any {
        this[EventType::class] = lambda
        return lambda
    }
   // ... (rest of the code remains the same)
}

新语法

这就是使用新语法注册事件处理程序的效果:

register<BlockEvent.OnChangeLineWidth> {
    engine.block.setWidth(block, engine.block.getFrameWidth(block))
    engine.block.setHeight(block, it.width)
}

好多了, 是不是? 新的语法更加简洁, 消除了冗余, 而且是类型安全的, 因为重新定义的类型参数确保了事件类型在编译时和运行时都是已知的, 从而消除了不安全铸造的需要.

步骤 5: 将register提升为高亮显示的扩展函数

为了提高代码的可读性, 我们将采取一个微妙但有效的步骤, 将register函数从一个EventsHandler类函数转换为一个EventsHandler扩展函数.

听起来很蠢! 为什么呢?

这个小改动通过语法高亮显示 Kotlin 扩展函数中的register关键字, 提高了代码的可读性. 这将使代码更加丰富多彩, 从而提高可读性.

更新了EventsHandler

EventsHandler类在很大程度上保持不变, 但register函数现在在类之外, 并转化为EventsHandler类的扩展函数`:

class EventsHandler(
    register: EventsHandler.() -> Unit,
) {
    // ... (rest of the code remains the same)
}

inline fun <reified EventType : BaseEvent> EventsHandler.register(noinline lambda: (event: EventType) -> Unit) : Any {
    this[EventType::class] = lambda
    return lambda
}

只需将register移出类,EventsHandler类的定义就会以独特的语法高亮显示出来. 这是一个巧妙的技巧, 不会影响运行时或编译期的性能, 因为这是一个内联操作.

register<BlockEvent.OnChangeLineWidth> {
    engine.block.setWidth(block, engine.block.getFrameWidth(block))
    engine.block.setHeight(block, it.width)
}

第 6 步: 使用委托属性消除lateinit变量

现在, 是时候解决神秘的lateinit变量和有点复杂的fillPayload机制了. 让我们引入一种更简洁的方法, 使用委托属性和 lambda 函数来注入依赖关系.

让我们添加一个Inject类, 将普通的 lambda 包装成可委托的:

class Inject<Type>(private val inject: () -> Type) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Type = inject()
}

有了这种新发现的能力, 我们的事件处理程序代码变得更简洁, 更直观. 它采用了 Jetpack Compose 的声明式语法:

fun EventsHandler.textBlockEvents(
    engine: () -> Engine,
    block: () -> DesignBlock,
    fontFamilyMap: () -> Map<String, FontFamilyData>,
) {
    // Inject the dependencies
    val engine by Inject(engine)
    val block by Inject(block)
    val fontFamilyMap by Inject(fontFamilyMap)

    // Event handling logic here
    // ...
}

每当访问其中一个变量时, lambda 就会被调用, 并始终获得当前变量.

此外, "payload"的创建也变得更加直接, 简洁和类型安全. 看起来有点像传递变量:

private val eventHandler = EventsHandler {
    textBlockEvents (
        engine = ::engine,
        block = ::getBlockForEvents,
        fontFamilyMap = { checkNotNull(assetsRepo.fontFamilies.value).getOrThrow() },
    )
}

看起来感觉就像变戏法一样! 很酷吧?

第 7 步: 单一责任原则的多个事件处理

在压轴部分, 我们将利用之前更改中新发现的灵活性来注册多个事件处理程序函数. 现在, 每个事件处理程序注册函数都有一个特定的主题, 完全符合单一责任原则(SRP).

增强的事件处理程序注册

现在, 我们可以在同一个EventsHandler实例中注册多个事件处理程序函数. 每个函数都可以专门处理特定类型的事件, 从而使代码更加模块化和易于管理. 看看这个宏伟的设计:

private val eventHandler = EventsHandler {
    cropEvents(
        engine = ::engine,
        block = ::getBlockForEvents,
    )
    blockEvents (
        engine = ::engine,
        block = ::getBlockForEvents,
    )
    textBlockEvents (
        engine = ::engine,
        block = ::getBlockForEvents,
        fontFamilyMap = { checkNotNull(assetsRepo.fontFamilies.value).getOrThrow() },
    )
    // ...
}

fun EventsHandler.blockEvents(
    engine: () -> Engine,
    block: () -> DesignBlock
) {
    val engine: Engine by Inject(engine)
    val block: DesignBlock by Inject(block)

    register<BlockEvent.OnDelete> { engine.delete(block) }

    register<BlockEvent.OnBackward> { engine.sendBackward(block) }

    register<BlockEvent.OnDuplicate> { engine.duplicate(block) }

    register<BlockEvent.OnForward> { engine.bringForward(block) }

    register<BlockEvent.ToBack> { engine.sendToBack(block) }

    register<BlockEvent.ToFront> { engine.bringToFront(block) }

    register<BlockEvent.OnChangeFinish> { engine.editor.addUndoStep() }

    register<BlockEvent.OnChangeBlendMode> {
        if (engine.block.getBlendMode(block) != it.blendMode) {
            engine.block.setBlendMode(block, it.blendMode)
            engine.editor.addUndoStep()
        }
    }

    register<BlockEvent.OnChangeOpacity> { engine.block.setOpacity(block, it.opacity) }
}

fun EventsHandler.cropEvents(
    engine: () -> Engine,
    block: () -> DesignBlock
) {
    val engine: Engine by Inject(engine)
    val block: DesignBlock by Inject(block)
    // ... (event handling logic for cropping events)
}

fun EventsHandler.textBlockEvents(
    engine: () -> Engine,
    block: () -> DesignBlock,
    fontFamilyMap: () -> Map<String, FontFamilyData>,
) {
    val engine by Inject(engine)
    val block by Inject(block)
    val fontFamilyMap by Inject(fontFamilyMap)
    // ... (event handling logic for text block events)
}

在代码的触发及其 API 保持不变的同时, 也不需要传递额外的参数:

open fun onEvent(event: Event) {
    eventHandler.handleEvent(event)
}

最后的话

在我们结束 Kotlin 代码重构之旅的时候, 我们已经揭开了增强性能和风格的神秘面纱. 通过采用HashMap, infix 函数和带有重定义类型参数的内联函数等技术, 我们将代码提升到了新的高度. 这样做的好处显而易见: 提高了效率, 可读性并遵循了单一责任原则. 有了这些工具, 你现在就可以开始自己的编码冒险, 将凌乱的代码变成优雅的杰作.

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题

图片

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值