Release top efficiency in Kotlin code with those professional refactoring pointers.
On this planet of device building, code refactoring is the hero that rescues us from tangled and inefficient code. On this article, we’ll embark on an journey to redesign Kotlin code dealing with various occasions. Our project? To strengthen efficiency and elegance, making the code sleeker, extra maintainable, and a pleasure to paintings with.
In this adventure to develop into Kotlin match dealing with, our function is to refine our code to be extra environment friendly, readable, and maintainable. We’re introducing a lot of enhancements, together with:
- Changing a convoluted
when
remark with aHashMap
for lightning-fast (O(1)) efficiency. - Infusing syntactic sweetness with inline purposes and reified form parameters.
- Using delegated houses for cleaner dependency injection.
- Adhering to the Unmarried Duty Idea by way of enabling more than one specialised match handler purposes.
Our journey starts with a look on the unique code. This codebase manages a lot of block occasions thru a serve as named handleBlockEvent
and an match handler serve as referred to as onEvent
. Let’s unveil the unique code:
open a laugh onEvent(match: Tournament) {
// ...
handleBlockEvent(engine, getBlockForEvents(), checkNotNull(assetsRepo.fontFamilies.worth).getOrThrow())
}a laugh handleBlockEvent(engine: Engine, block: DesignBlock, fontFamilyMap: Map<String, FontFamilyData>, match: BlockEvent) {
when (match) {
BlockEvent.OnDelete -> engine.delete(block)
BlockEvent.OnBackward -> engine.sendBackward(block)
BlockEvent.OnDuplicate -> engine.reproduction(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, match.blendMode)
is BlockEvent.OnChangeOpacity -> engine.block.setOpacity(block, match.opacity)
is BlockEvent.OnChangeFillColor -> onChangeFillColor(engine, block, match.colour)
// and so forth...
}
}
sealed magnificence BlockEvent : Tournament {
object OnChangeFinish : BlockEvent
object OnForward : BlockEvent
object OnBackward : BlockEvent
object OnDuplicate : BlockEvent
object OnDelete : BlockEvent
object ToFront : BlockEvent
object ToBack : BlockEvent
information magnificence OnChangeBlendMode(val blendMode: BlendMode) : BlockEvent
information magnificence OnChangeOpacity(val opacity: Go with the flow) : BlockEvent
information magnificence OnChangeFillColor(val colour: Colour) : BlockEvent
// and so forth...
}
To make use of the unique code, you’d in most cases name the onEvent
serve as with a particular match:
onEvent(BlockEvent.OnChangeFillColor(Colour.RED))
This may then cause the handleBlockEvent
serve as to take care of the development to hand. Now, let’s embark on our first refactoring journey.
In our first act of refactoring, we introduce a trusty HashMap
to map every match form to its corresponding motion. This heroic transfer gets rid of the will for the convoluted when
remark, making our code extra environment friendly. We additionally unveil a payload mechanism to put across crucial information to the development handlers.
Behold the refactored code:
summary magnificence EventsHandler<Payloads>(
val fillPayload: (cache: Payloads) -> Unit
) {
summary val payloadCache: Payloads
personal val eventMap = mutableMapOf<KClass<out Tournament>, Payloads.(match: Tournament) -> Unit>()a laugh handleEvent(match: Tournament) {
eventMap[event::class]?.let {
it.invoke(payloadCache.additionally { fillPayload(it) }, match)
}
}
operator a laugh <EventType : Tournament> set(match: KClass<out EventType>, lambda: Payloads.(match: EventType) -> Unit) {
eventMap[event] = lambda as Payloads.(match: Tournament) -> Unit
}
}
magnificence BlockEventsHandler(fillPayload: (cache: BlockEventsHandler.Payloads) -> Unit) : EventsHandler<BlockEventsHandler.Payloads>(fillPayload) {
magnificence 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.reproduction(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.colour) }
// and so forth...
}
}
personal val blockEventHandler = BlockEventsHandler {
it.engine = engine
it.block = getBlockForEvents()
it.fontFamilyMap = checkNotNull(assetsRepo.fontFamilies.worth).getOrThrow()
}
open a laugh onEvent(match: Tournament) {
// ...
blockEventHandler.handleEvent(match)
}
Via harnessing the ability of a HashMap
, now we have turbocharged our match dealing with. The time complexity for dealing with an match is now a lightning-fast (O(1)), a huge growth over the (O(n)) time complexity of the ponderous when
remark. Whilst our payload mechanism provides a dollop of syntactic sugar. It permits us to package all of the important information right into a unmarried object, making our code extra legible and maintainable.
💡 Word: The usage of a HashMap as a substitute of a giant when() remark supplies an important efficiency growth. It may be as much as 40 to 150 instances quicker. Then again, explaining the main points would exceed the scope of this weblog submit. Due to this fact, I can duvet it, at the side of different Kotlin efficiency puzzles, in a long run weblog submit.
Whilst the refactored code stays so simple as prior to:
onEvent(BlockEvent.OnChangeFillColor(Colour.RED))
This nonetheless triggers the handleEvent
manner in BlockEventsHandler
, which in flip plays the proper motion according to the development form. The BlockEvent
itself is an information object containing all match main points, and it serves because the lambda parameter.
The payload introduction is a dynamic lambda serve as that’s done every time an match is treated. This guarantees that every one variables now not a part of the development are persistently up-to-date. For the reason that we’re coping with a unmarried thread in line with match handler, caching the payload is solely protected.
In our subsequent act, we raise our syntax to a brand new stage of expressiveness and clarity. We introduce an infix serve as referred to as to
, permitting us to map an match magnificence to its corresponding motion elegantly.
Witness the up to date code:
summary magnificence EventsHandler<Payloads>(
val fillPayload: (cache: Payloads) -> Unit
) {
infix a laugh <Payloads, EventType : Tournament> KClass<out EventType>.to(lambda: Payloads.(match: EventType) -> Unit) {
eventMap[event] = lambda as Payloads.(match: Tournament) -> Unit
}
// ... (remainder of the code stays the similar)
}magnificence BlockEventsHandler(
supervisor: EventsManager,
override val fillPayload: (cache: TextBlockEventsHandler) -> Unit
) : EventsHandler<TextBlockEventsHandler>(supervisor) {
lateinit var engine: Engine
lateinit var block: DesignBlock
lateinit var fontFamilyMap: Map<String, FontFamilyData>
init {
BlockEvent.OnDelete::magnificence to {
engine.delete(block)
}
BlockEvent.OnBackward::magnificence to {
engine.sendBackward(block)
}
BlockEvent.OnDuplicate::magnificence to {
engine.reproduction(block)
}
BlockEvent.OnForward::magnificence to {
engine.bringForward(block)
}
BlockEvent.ToBack::magnificence to {
engine.sendToBack(block)
}
BlockEvent.ToFront::magnificence to {
engine.bringToFront(block)
}
BlockEvent.OnChangeFinish::magnificence to {
engine.editor.addUndoStep()
}
BlockEvent.OnChangeBlendMode::magnificence to {
onChangeBlendMode(engine, block, it.blendMode)
}
BlockEvent.OnChangeOpacity::magnificence to {
engine.block.setOpacity(block, it.opacity)
}
BlockEvent.OnChangeFillColor::magnificence to {
onChangeFillColor(engine, block, it.colour)
}
// ...
}
}
The advent of the to
infix serve as provides a sprinkle of syntactic sweetness that complements code expressiveness and permits a extra herbal utilization. This makes it crystal transparent what every match is all about. And worry now not, the efficiency stays at a blazing-fast (O(1)), because of our trusty HashMap.
Whilst the to
key phrase is used right here, be at liberty to replace it with different phrases like take care of
, cause
, or anything else that most closely fits your context. Flexibility is the secret.
Then again, that is nonetheless now not highest since the ::magnificence
breaks clean studying.
So let’s do it otherwise. Allow us to attempt to introduce a extra chic method to sign in an match. Allow us to get rid of the wish to specify ::magnificence
each time we sign in an match handler will make our code extra concise and readable.
That is made imaginable by way of an inline serve as with a verified form parameter that maintains the category reference at runtime.
To try this, we prolong the EventsHandler
magnificence with this new sign in
serve as:
magnificence EventsHandler(
sign in: EventsHandler.() -> Unit,
) {
inline a laugh <reified EventType : BaseEvent> sign in(noinline lambda: (match: EventType) -> Unit) : Any {
this[EventType::class] = lambda
go back lambda
}
// ... (remainder of the code stays the similar)
}
That is what registering an match handler seems like with the brand new syntax:
sign in<BlockEvent.OnChangeLineWidth> {
engine.block.setWidth(block, engine.block.getFrameWidth(block))
engine.block.setHeight(block, it.width)
}
A lot better, proper? The brand new syntax is extra concise, gets rid of redundancy, and is type-safe since the reified form parameters make certain that the development form is understood at compile-time and runtime, getting rid of the will for unsafe casting.
To strengthen code clarity, we’ll make a delicate however efficient step by way of changing the sign in
serve as from a EventsHandler
magnificence serve as, into an EventsHandler
extension serve as.
Sounds silly! So why?
This small trade improves code clarity by way of highlighting the sign in
key phrase thru syntax highlighting from a Kotlin extension serve as. This will likely make it a lot more colourful, which improves clarity.
The EventsHandler
magnificence stays in large part unchanged, however the sign in
serve as is now out of doors the category and remodeled into an extension serve as for the EventsHandler
magnificence:
magnificence EventsHandler(
sign in: EventsHandler.() -> Unit,
) {
// ... (remainder of the code stays the similar)
}inline a laugh <reified EventType : BaseEvent> EventsHandler.sign in(noinline lambda: (match: EventType) -> Unit) : Any {
this[EventType::class] = lambda
go back lambda
}
Via merely moving sign in
out of the category, the EventsHandler
magnificence definition now stands proud with unique syntax highlighting. It is a artful trick that does not affect runtime or assemble efficiency, since it is an inline operation anyway.
sign in<BlockEvent.OnChangeLineWidth> {
engine.block.setWidth(block, engine.block.getFrameWidth(block))
engine.block.setHeight(block, it.width)
}
Now, it’s time to handle the enigmatic lateinit
variables and the quite convoluted fillPayload
mechanism. Allow us to introduce a cleaner means, the usage of delegated houses and lambda purposes to inject dependencies.
Let’s upload an Inject
magnificence to wrap a typical lambda as delegable:
magnificence Inject<Kind>(personal val inject: () -> Kind) {
operator a laugh getValue(thisRef: Any?, assets: KProperty<*>): Kind = inject()
}
With this newfound energy, our match handler code turns into cleaner and extra intuitive. It takes at the taste of Jetpack Compose’s declarative syntax:
a laugh EventsHandler.textBlockEvents(
engine: () -> Engine,
block: () -> DesignBlock,
fontFamilyMap: () -> Map<String, FontFamilyData>,
) {
// Inject the dependencies
val engine by way of Inject(engine)
val block by way of Inject(block)
val fontFamilyMap by way of Inject(fontFamilyMap)// Tournament dealing with good judgment right here
// ...
}
Each time some of the variables is accessed, the lambda is named, and also you all the time get the present variable.
Additionally, the introduction of the “payload” turns into easier, blank, and type-safe. It kinda seems like passing a variable:
personal val eventHandler = EventsHandler {
textBlockEvents (
engine = ::engine,
block = ::getBlockForEvents,
fontFamilyMap = { checkNotNull(assetsRepo.fontFamilies.worth).getOrThrow() },
)
}
Seems to be and looks like magic! Beautiful cool, proper?
In our grand finale, we harness the newfound flexibility from our earlier adjustments to sign in more than one match handler purposes. Each and every match handler registration serve as now has a particular subject, aligning completely with the Unmarried Duty Idea (SRP).
We will be able to now sign in more than one match handler purposes inside the similar EventsHandler
example. Each and every serve as can focus on dealing with a specific form of match, making the code extra modular and manageable. Behold the grand design:
personal val eventHandler = EventsHandler {
cropEvents(
engine = ::engine,
block = ::getBlockForEvents,
)
blockEvents (
engine = ::engine,
block = ::getBlockForEvents,
)
textBlockEvents (
engine = ::engine,
block = ::getBlockForEvents,
fontFamilyMap = { checkNotNull(assetsRepo.fontFamilies.worth).getOrThrow() },
)
// ...
}a laugh EventsHandler.blockEvents(
engine: () -> Engine,
block: () -> DesignBlock
) {
val engine: Engine by way of Inject(engine)
val block: DesignBlock by way of Inject(block)
sign in<BlockEvent.OnDelete> { engine.delete(block) }
sign in<BlockEvent.OnBackward> { engine.sendBackward(block) }
sign in<BlockEvent.OnDuplicate> { engine.reproduction(block) }
sign in<BlockEvent.OnForward> { engine.bringForward(block) }
sign in<BlockEvent.ToBack> { engine.sendToBack(block) }
sign in<BlockEvent.ToFront> { engine.bringToFront(block) }
sign in<BlockEvent.OnChangeFinish> { engine.editor.addUndoStep() }
sign in<BlockEvent.OnChangeBlendMode> {
if (engine.block.getBlendMode(block) != it.blendMode) {
engine.block.setBlendMode(block, it.blendMode)
engine.editor.addUndoStep()
}
}
sign in<BlockEvent.OnChangeOpacity> { engine.block.setOpacity(block, it.opacity) }
}
a laugh EventsHandler.cropEvents(
engine: () -> Engine,
block: () -> DesignBlock
) {
val engine: Engine by way of Inject(engine)
val block: DesignBlock by way of Inject(block)
// ... (match dealing with good judgment for cropping occasions)
}
a laugh EventsHandler.textBlockEvents(
engine: () -> Engine,
block: () -> DesignBlock,
fontFamilyMap: () -> Map<String, FontFamilyData>,
) {
val engine by way of Inject(engine)
val block by way of Inject(block)
val fontFamilyMap by way of Inject(fontFamilyMap)
// ... (match dealing with good judgment for textual content block occasions)
}
Whilst the triggering and its API stay unchanged, and no additional parameters wish to be handed:
open a laugh onEvent(match: Tournament) {
eventHandler.handleEvent(match)
}
As we conclude our adventure thru Kotlin code refactoring, we’ve unlocked the secrets and techniques to enhanced efficiency and elegance. Via embracing ways akin to HashMaps, infix purposes, and inline purposes with reified form parameters, we’ve increased our code to new heights. The advantages are transparent: advanced potency, clarity, and adherence to the Unmarried Duty Idea. Armed with those equipment, you’re now in a position to embark by yourself coding adventures, reworking messy code into chic masterpieces.
When you’d like to take a look at it out, I’ve created a running instance code at the Kotlin Playground.
Thanks for accompanying, and glad coding! By no means fail to spot my paintings at IMG.LY, and our updates. Subscribe to our e-newsletter.