They say that "design patterns are workarounds for the shortcomings of a particular programming language." An interesting proposition, if only it had not been said by Lisp and Schema apologists .
But it seems that the developers of the Kotlin language have taken this statement really to heart.
Of course, the first pattern that comes to mind is the Loner. And it is built right into the language in the form of the object keyword:
object JustSingleton { val value : String = "Just a value" }
Now the JustSingleton.value
field will be available from anywhere in the package.
And no, this is not a static initialization, as it may seem. Let's try to initialize this field with some delay inside:
object SlowSingleton { val value : String init { var uuid = "" val total = measureTimeMillis { println("Computing") for (i in 1..10_000_000) { uuid = UUID.randomUUID().toString() } } value = uuid println("Done computing in ${total}ms") } }
Lazy initialization occurs on the first call:
@org.testng.annotations.Test fun testSingleton() { println("Test started") for (i in 1..3) { val total = measureTimeMillis { println(SlowSingleton.value) } println("Took $total ms") } }
At the output we get:
Test started Computing Done computing in 5376ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 5377 ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 0 ms "45f7d567-9b3e-4099-98e6-569ebc26ecdf" Took 0 ms
Note that if you do not use this object, the operation takes 0 ms, although the object is still defined in your code.
val total = measureTimeMillis { //println(SlowSingleton.value) }
At the exit:
Test started Took 0 ms Took 0 ms Took 0 ms
Then comes the decorator . This is a pattern that allows you to add a bit of functionality on top of some other class. Yes, IntelliJ can create it for you. But Kotlin went even further.
How about every time you add a new key in the HashMap, we get a message about it?
In the constructor, you define an instance that you delegate to all methods using the by keyword.
/** * Using `by` keyword you can delegate all but overridden methods */ class HappyMap<K, V>(val map : MutableMap<K, V> = mutableMapOf()) : MutableMap<K, V> by map{ override fun put(key: K, value: V): V? { return map.put(key, value).apply { if (this == null) { println("Yay! $key") } } } }
Note that we can access the elements of our map through square brackets and use all other methods in the same way as in the usual HashMap.
@org.testng.annotations.Test fun testDecorator() { val map = HappyMap<String, String>() val result = captureOutput { map["A"] = "B" map["B"] = "C" map["A"] = "C" map.remove("A") map["A"] = "C" } assertEquals(mapOf("A" to "C", "B" to "C"), map.map) assertEquals(listOf("Yay! A", "Yay! B", "Yay! A"), (result)) }
Companion object makes it easy to implement the Factory Method . This is the pattern with which the object controls its initialization process in order to hide some secrets within itself.
class SecretiveGirl private constructor(val age: Int, val name: String = "A girl has no name", val desires: String = "A girl has no desires") { companion object { fun newGirl(vararg desires : String) : SecretiveGirl { return SecretiveGirl(17, desires = desires.joinToString(", ")) } fun newGirl(name : String) : SecretiveGirl { return SecretiveGirl(17, name = name) } } }
Now no one can change the age of SecretiveGirl:
@org.testng.annotations.Test fun FactoryMethodTest() { // Cannot do this, constructor is private // val arya = SecretiveGirl(); val arya1 = SecretiveGirl.newGirl("Arry") assertEquals(17, arya1.age) assertEquals("Arry", arya1.name) assertEquals("A girl has no desires", arya1.desires) val arya2 = SecretiveGirl.newGirl("Cersei Lannister", "Joffrey", "Ilyn Payne") assertEquals(17, arya2.age) assertEquals("A girl has no name", arya2.name) assertEquals("Cersei Lannister, Joffrey, Ilyn Payne", arya2.desires) }
The last one for today is Strategy . Since Kotlin has high-order functions , implementing this pattern is also very simple:
class UncertainAnimal { var makeSound = fun () { println("Meow!") } }
And dynamically change the behavior:
@org.testng.annotations.Test fun testStrategy() { val someAnimal = UncertainAnimal() val output = captureOutput { someAnimal.makeSound() someAnimal.makeSound = fun () { println("Woof!") } someAnimal.makeSound() } assertEquals(listOf("Meow!", "Woof!"), output) }
Please note that this is indeed the Strategy pattern, and you cannot change the method signature (hello, JS!)
// Won't compile! someAnimal.makeSound = fun (message : String) { println("$message") }
All code is available on my GitHub page .
And if you are interested in learning more about Kotlin and the design patterns embedded in it, there is an excellent book “Kotlin in Action” . You will like it, even if you do not plan to use this language in the near future (although there is no reason not to).
Source: https://habr.com/ru/post/421873/
All Articles