Kotlin Design Patterns

Kotlin Design Patterns


Sie sagen, dass "Entwurfsmuster Problemumgehungen für die Mängel einer bestimmten Programmiersprache sind." Ein interessanter Vorschlag, wenn er nur von den Apologeten Lisp und Schema nicht gesagt worden wäre .


Aber es scheint, dass sich die Entwickler der Kotlin-Sprache diese Aussage wirklich zu Herzen genommen haben.


Singleton


Das erste Muster, das mir in den Sinn kommt, ist natürlich Loner. Und es ist direkt in die Sprache in Form des Objektschlüsselworts eingebaut:


object JustSingleton { val value : String = "Just a value" } 

Jetzt kann von überall im Paket auf das Feld JustSingleton.value werden.


Und nein, dies ist keine statische Initialisierung, wie es scheinen mag. Versuchen wir, dieses Feld mit einer gewissen Verzögerung zu initialisieren:


 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") } } 

Beim ersten Aufruf erfolgt eine verzögerte Initialisierung:


 @org.testng.annotations.Test fun testSingleton() { println("Test started") for (i in 1..3) { val total = measureTimeMillis { println(SlowSingleton.value) } println("Took $total ms") } } 

Die Ausgabe ist:


 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 

Bitte beachten Sie, dass der Vorgang 0 ms dauert, wenn Sie dieses Objekt nicht verwenden, obwohl das Objekt noch in Ihrem Code definiert ist.


 val total = measureTimeMillis { //println(SlowSingleton.value) } 

Ausgabe:


 Test started Took 0 ms Took 0 ms Took 0 ms 

Dekorateur


Dann kommt der Dekorateur . Dies ist ein Muster, mit dem Sie zusätzlich zu einer anderen Klasse ein wenig Funktionalität hinzufügen können. Ja, IntelliJ kann es für Sie erstellen. Aber Kotlin ging noch weiter.


Wie wäre es, wenn wir jedes Mal, wenn wir einen neuen Schlüssel in die HashMap einfügen, eine Nachricht darüber erhalten?


Im Konstruktor definieren Sie eine Instanz, an die alle Methoden mit dem Schlüsselwort by delegiert werden.


 /** * 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") } } } } 

Beachten Sie, dass wir über eckige Klammern auf die Elemente unserer Karte zugreifen und alle anderen Methoden auf dieselbe Weise wie in einer regulären HashMap verwenden können.


 @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)) } 

Fabrikmethode


Das Companion-Objekt erleichtert die Implementierung der Factory-Methode . Dies ist das Muster, nach dem das Objekt seinen Initialisierungsprozess steuert, um einige Geheimnisse in sich selbst zu verbergen.


 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) } } } 

Jetzt kann niemand das Alter von SecretiveGirl ändern:


 @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) } 

Strategie


Der letzte für heute ist Strategie . Da Kotlin über Funktionen höherer Ordnung verfügt , ist die Implementierung dieses Musters ebenfalls sehr einfach:


 class UncertainAnimal { var makeSound = fun () { println("Meow!") } } 

Und das Verhalten dynamisch ändern:


 @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) } 

Bitte beachten Sie, dass dies tatsächlich ein Strategiemuster ist und Sie die Methodensignatur nicht ändern können (Hallo, JS!)


 // Won't compile! someAnimal.makeSound = fun (message : String) { println("$message") } 

Der gesamte Code ist auf meiner GitHub-Seite verfügbar.


Und wenn Sie mehr über Kotlin und seine integrierten Designmuster erfahren möchten, gibt es ein großartiges Buch mit dem Titel „Kotlin in Aktion“ . Sie werden es auch dann mögen, wenn Sie nicht vorhaben, diese Sprache in naher Zukunft zu verwenden (obwohl es keinen Grund gibt, dies nicht zu tun).

Source: https://habr.com/ru/post/de421873/


All Articles