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

高雅的Kotlin事情处理 - 重构

在软件开发的世界里, 代码重构是将咱们从纠结而低效的代码中解救出来的英豪. 在本文中, 咱们将开端一场冒险, 用以重构处理各种事情的 Kotlin 代码. 咱们的任务是什么? 进步功能和风格, 使代码更流通, 更易保护, 并让咱们的作业充满趣味.

咱们的方针

在改造 Kotlin 事情处理的过程中, 咱们的方针是完善代码, 使其更高效, 更易读, 更易保护. 咱们将引进各种改进, 包含:

  • HashMap替代杂乱的when句子, 以获得快如闪电的(O(1))功能.
  • 运用内联函数和reified类型参数来优化语法.
  • 选用托付属性, 完成更简练的依靠注入.
  • 经过启用多个专门的事情处理函数, 坚持单一职责准则.

第 1 步: 起跑线

高雅的Kotlin事情处理 - 重构

咱们的冒险之旅从查看原始代码开端. 该代码库经过名为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的奥秘面纱, 完成峰值功能

高雅的Kotlin事情处理 - 重构

在第一次重构中, 咱们引进了值得信任的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 函数增加语法糖

高雅的Kotlin事情处理 - 重构

在下一步中, 咱们将把语法的表现力和可读性进步到一个新的水平. 咱们引进了一个名为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 步: 运用内联函数以坚持高雅

高雅的Kotlin事情处理 - 重构

但是, 这依然不够完美, 由于::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进步为高亮显示的扩展函数

高雅的Kotlin事情处理 - 重构

为了进步代码的可读性, 咱们将采取一个奇妙但有效的过程, 将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变量

高雅的Kotlin事情处理 - 重构

现在, 是时分处理奥秘的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 步: 单一职责准则的多个事情处理

高雅的Kotlin事情处理 - 重构

在压轴部分, 咱们将使用之前更改中新发现的灵活性来注册多个事情处理程序函数. 现在, 每个事情处理程序注册函数都有一个特定的主题, 完全符合单一职责准则(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 函数和带有重界说类型参数的内联函数等技能, 咱们将代码进步到了新的高度. 这样做的优点清楚明了: 进步了功率, 可读性并遵从了单一职责准则. 有了这些工具, 你现在就能够开端自己的编码冒险, 将凌乱的代码变成高雅的创作.

Stay GOLD!