Kotlin Design Patterns(번역)

Kotlin design patterns

원문 : https://proandroiddev.com/kotlin-design-patterns-8e152540ee2c

Kotlin Design Patterns

This article was originally posted on June 2017 on LinkedIn

이런 말이 있습니다. “디자인 패턴은 특정 언어의 단점을 해결하는 방법이다.” 1996년도 쯔음 Lisp나 Scheme 옹호자들이 한 말이 아니라면, 매우 흥미로울 것입니다.

하지만, Kotlin 언어 디자이너들은 이 말에 진심으로 감명을 받은거 같습니다.

Singleton

물론 마음속에 떠오르는 첫 번째 디자인 패턴은 싱글톤 입니다. 그리고 이것은 바로 언어 자체에 내장되어 있습니다. object 를 보세요:

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

이제 하나의 JustSingleton이 생겼습니다. value는 패키지 어디서든 사용 가능합니다.

그리고, 생각할지도 모르겠지만, 정적 초기화가 아닙니다. 안에서 뭔가 시간이 걸리는 일을 해봅시다:

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

최초 호출될 때 느리게 초기화 됩니다:

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

출력:

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 0ms

object가 코드상에 정의 되어 있더라도, 당신이 그것을 사용하지 않으면 이 작업은 0ms에 완료 됩니다.

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

출력:

Test started
Took 0 ms
Took 0 ms
Took 0 ms

Decorator

다음은 데코레이터 입니다. 이미 알고 계신것처럼, 어떤 다른 클래스에 약간의 기능을 추가할 수 있게 해줍니다. IntelliJ가 당신을 위해 그것을 생성해 줄 수 있습니다. 예~. 이제 한걸음 더 나아갑니다.

새로운 키가 추가될 때 마다 환호하는 HashMap을 만들어 볼까요?

지원 인스턴스를 컨스트럭터에 정의하고, by 키워드로 모든 메서드를 위임할 수 있습니다.

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

우리는 우리의 맵에 대괄호를 이용하여 값에 접근할 수 있고, 다른 모든 메서드는 기존과 동일하게 동작한다는 것을 명심하세요:

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

FactoryMethod

Companion object 를 사용하여 손쉽게 팩터리 메서드 를 구현할 수 있습니다. 객체가 초기화 되는것을 컨트롤 하려는 유일한 객체 입니다. 아마도 그 안에서 비밀스러운 어떤 일을 하기 때문일 겁니다.

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

이제 아무도 SecretiveGirl의 나이를 변경할 수 없습니다 :

@org.testng.annotations.Test
fun FactoryMehtodTest() {
    // 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)
}

Strategy

오늘 마지막은 전략 입니다. Kotlin은 고차함수 를 가지고 있어서 이 역시 매우 쉽습니다:

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

그리고 런타임에 동작을 바꾸려면:

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

이것은 전략 패턴이고, 시그니처를 바꾸는 것은 동작하지 않습니다.

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

평소대로, 모든 코드는 제 GitHub 페이지에 있습니다:

github.com/alexeysoshin/KotlinDesignPatterns

그리고, Kotlin과 디자인 패턴을 배우고 싶다면 아주 훌륭한 “Kotlin in Action” 책 이 있습니다. 비록 곧 이 언어를 사용할 계획이 없더라도, 재미있는 읽을거리 입니다.


Posts

Pages


© 2017. All rights reserved.

Powered by Hydejack v7.5.0