Unlock Peak Kotlin Performance with Advanced Refactoring Techniques
This article guides developers through a series of Kotlin refactoring steps—replacing when statements with HashMap, introducing payload mechanisms, infix functions, inline registration, and delegated properties—to dramatically improve event‑handling performance, readability, and maintainability while adhering to the single‑responsibility principle.
In the world of software development, refactoring is the hero that rescues code from inefficiency. This tutorial embarks on a Kotlin adventure to refactor event‑handling code, aiming for higher performance, cleaner style, and easier maintenance.
Our Goal
We aim to transform Kotlin event handling by introducing HashMap‑based dispatch, payload mechanisms, infix functions, inline registration, and delegated properties, all while following the single‑responsibility principle.
Step 1: Baseline Code
The original implementation uses a when statement to handle a sealed BlockEvent hierarchy:
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...
}Step 2: Replace when with HashMap
We introduce a mutable map that links each event class to its handling lambda, eliminating the O(n) when lookup and achieving O(1) dispatch:
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)
}This change yields constant‑time dispatch and bundles required data into a payload object for clearer, safer code.
Step 3: Add an Infix to Function
We define an infix extension that maps a KClass to its handling lambda, making the registration syntax more expressive:
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 unchanged)
}
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) }
// ...
}
}Step 4: Inline Registration without ::class
We create an inline register function with a reified type parameter, allowing registration without explicitly writing ::class:
class EventsHandler(
register: EventsHandler.() -> Unit,
) {
inline fun <reified EventType : BaseEvent> register(noinline lambda: (event: EventType) -> Unit) : Any {
this[EventType::class] = lambda
return lambda
}
// ... (rest unchanged)
}
register<BlockEvent.OnChangeLineWidth> {
engine.block.setWidth(block, engine.block.getFrameWidth(block))
engine.block.setHeight(block, it.width)
}Step 5: Make register an Extension Function
Moving register outside the class highlights it as an extension, improving readability while keeping performance unchanged:
class EventsHandler(
register: EventsHandler.() -> Unit,
) { /* unchanged */ }
inline fun <reified EventType : BaseEvent> EventsHandler.register(noinline lambda: (event: EventType) -> Unit) : Any {
this[EventType::class] = lambda
return lambda
}Step 6: Replace lateinit with Delegated Properties
We introduce an Inject delegate that lazily provides dependencies, removing the need for lateinit variables and making the payload creation more declarative:
class Inject<Type>(private val inject: () -> Type) {
operator fun getValue(thisRef: Any?, property: KProperty<*>) : Type = inject()
}
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 here
}Step 7: Register Multiple Handlers Respecting SRP
By composing several small handler functions— blockEvents, cropEvents, textBlockEvents —inside a single EventsHandler instance, we keep each responsibility isolated while sharing the same dispatch mechanism:
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) }
// ... other registrations ...
}Finally, the public onEvent simply forwards the event to the composed handler, preserving the original API while benefiting from the refactored architecture.
Conclusion
By systematically applying HashMap‑based dispatch, payload objects, infix and inline functions, and delegated property injection, the Kotlin event‑handling code achieves dramatically better performance (constant‑time lookup), clearer syntax, and strict adherence to the single‑responsibility principle, empowering developers to write elegant, maintainable code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
