通过这些专业的重构技巧, 释放 Kotlin 代码的峰值性能.
在软件开发的世界里, 代码重构是将我们从纠结而低效的代码中解救出来的英雄. 在本文中, 我们将开始一场冒险, 用以重构处理各种事件的 Kotlin 代码. 我们的任务是什么? 提高性能和风格, 使代码更流畅, 更易维护, 并让我们的工作充满乐趣.
我们的目标
在改造 Kotlin 事件处理的过程中, 我们的目标是完善代码, 使其更高效, 更易读, 更易维护. 我们将引入各种改进, 包括:
- 用
HashMap
代替复杂的when
语句, 以获得快如闪电的(O(1))性能. - 使用内联函数和reified类型参数来优化语法.
- 采用委托属性, 实现更简洁的依赖注入.
- 通过启用多个专门的事件处理函数, 坚持单一责任原则.
第 1 步: 起跑线
我们的冒险之旅从查看原始代码开始. 该代码库通过名为handleBlockEvent
的函数和名为onEvent
的事件处理函数来管理各种块事件. 让我们揭开原始代码的神秘面纱:
kotlin
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
函数:
less
onEvent(BlockEvent.OnChangeFillColor(Color.RED))
这将触发handleBlockEvent
函数来处理手头的事件. 现在, 让我们开始第一次重构冒险.
第 2 步: 揭开HashMap和Payload的神秘面纱, 实现峰值性能
在第一次重构中, 我们引入了值得信赖的HashMap
来将每个事件类型映射到其对应的操作. 这一英勇之举消除了对复杂的when
语句的需求, 使我们的代码更加高效. 我们还公布了一种Payload机制, 用于向事件处理程序传递重要数据.
请看重构后的代码:
kotlin
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 性能难题.
重构后的代码仍然和以前一样简单:
less
onEvent(BlockEvent.OnChangeFillColor(Color.RED))
这仍然会触发BlockEventsHandler
中的handleEvent
方法, 然后根据事件类型执行相应的操作. BlockEvent
本身是一个包含所有事件细节的数据对象, 它可以作为 lambda 参数.
关于Payload的说明
Payload创建是一个动态 lambda 函数, 每次处理事件时都会执行. 这样可以确保所有不属于事件的变量都是最新的. 鉴于我们处理的是每个事件处理程序的单线程, 缓存Payload是完全安全的.
第 3 步: 使用 Infix 函数增加语法糖
在下一步中, 我们将把语法的表现力和可读性提升到一个新的水平. 我们引入了一个名为to
的infix函数, 让我们可以优雅地将事件类映射到相应的动作.
请看更新后的代码:
kotlin
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
类:
kotlin
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)
}
新语法
这就是使用新语法注册事件处理程序的效果:
arduino
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
类的扩展函数`:
kotlin
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
类的定义就会以独特的语法高亮显示出来. 这是一个巧妙的技巧, 不会影响运行时或编译期的性能, 因为这是一个内联操作.
arduino
register<BlockEvent.OnChangeLineWidth> {
engine.block.setWidth(block, engine.block.getFrameWidth(block))
engine.block.setHeight(block, it.width)
}
第 6 步: 使用委托属性消除lateinit
变量
现在, 是时候解决神秘的lateinit
变量和有点复杂的fillPayload
机制了. 让我们引入一种更简洁的方法, 使用委托属性和 lambda 函数来注入依赖关系.
让我们添加一个Inject
类, 将普通的 lambda 包装成可委托的:
kotlin
class Inject<Type>(private val inject: () -> Type) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Type = inject()
}
有了这种新发现的能力, 我们的事件处理程序代码变得更简洁, 更直观. 它采用了 Jetpack Compose 的声明式语法:
kotlin
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"的创建也变得更加直接, 简洁和类型安全. 看起来有点像传递变量:
ini
private val eventHandler = EventsHandler {
textBlockEvents (
engine = ::engine,
block = ::getBlockForEvents,
fontFamilyMap = { checkNotNull(assetsRepo.fontFamilies.value).getOrThrow() },
)
}
看起来感觉就像变戏法一样! 很酷吧?
第 7 步: 单一责任原则的多个事件处理
在压轴部分, 我们将利用之前更改中新发现的灵活性来注册多个事件处理程序函数. 现在, 每个事件处理程序注册函数都有一个特定的主题, 完全符合单一责任原则(SRP).
增强的事件处理程序注册
现在, 我们可以在同一个EventsHandler
实例中注册多个事件处理程序函数. 每个函数都可以专门处理特定类型的事件, 从而使代码更加模块化和易于管理. 看看这个宏伟的设计:
kotlin
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 保持不变的同时, 也不需要传递额外的参数:
kotlin
open fun onEvent(event: Event) {
eventHandler.handleEvent(event)
}
最后的话
在我们结束 Kotlin 代码重构之旅的时候, 我们已经揭开了增强性能和风格的神秘面纱. 通过采用HashMap
, infix 函数和带有重定义类型参数的内联函数等技术, 我们将代码提升到了新的高度. 这样做的好处显而易见: 提高了效率, 可读性并遵循了单一责任原则. 有了这些工具, 你现在就可以开始自己的编码冒险, 将凌乱的代码变成优雅的杰作.
Stay GOLD!