Menu
Coddy logo textTech

데코레이터 패턴

Coddy Kotlin 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 47번째.

데코레이터 패턴은 동일한 인터페이스를 가진 다른 객체로 객체를 감싸 객체에 동작을 추가합니다. 데코레이터는 자신이 감싼 객체에 처리를 위임하고 자신의 부분을 추가하므로, 데코레이터를 여러 겹으로 쌓을 수 있습니다:

interface Coffee {
    fun cost(): Int
    fun description(): String
}
class Espresso : Coffee {
    override fun cost() = 200
    override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 50
    override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 30
    override fun description() = inner.description() + ", syrup"
}

main 안에서:

val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")

출력:

espresso, milk, milk, syrup: 330

데코레이터가 결과를 변경할 때는 순서가 중요합니다. 각 데코레이터는 자신 안에 있는 객체가 반환한 결과에 대해 작업합니다:

interface Text {
    fun render(): String
}
class Word(private val w: String) : Text {
    override fun render() = w
}
class Bracket(private val inner: Text) : Text {
    override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
    override fun render() = inner.render() + inner.render()
}

main 안에서:

println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())

출력:

[hi][hi]
[hihi]

by를 사용하면 데코레이터는 변경하는 멤버만 작성하고 나머지는 자동으로 전달합니다:

interface Source {
    fun read(): String
    fun name(): String
}
class FileSource(private val content: String) : Source {
    override fun read() = content
    override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
    override fun read() = inner.read().replace(word, "***")
}

main 내부:

val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")

출력:

notes.txt: the *** plan

데코레이터는 예를 들어 고객의 선택에 따라 프로그램이 실행되는 동안 결합되는 반면, 서브클래스는 코드를 작성할 때 하나의 조합을 고정합니다. 세 가지 추가 기능을 서브클래스로 구현하면 모든 조합마다 클래스를 만들어야 하지만, 데코레이터로 구현하면 fold가 목록에서 이를 차례로 쌓습니다:

interface Coffee {
    fun cost(): Int
}
class Espresso : Coffee {
    override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
    override fun cost() = inner.cost() + price
}

main 내부:

val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())

출력:

370
challenge icon

챌린지

쉬움

피자는 제공된 인터페이스 Pizza(price()와 toppings() 포함)의 데코레이터를 사용해 구성됩니다. Base(800, 토핑 없음)와 데코레이터 Cheese(+150, cheese 추가), Olives(+100, olives 추가), 그리고 내부의 모든 항목 가격을 두 배로 만들고 토핑을 두 번 반복하는 DoubleUp을 작성하세요. 그런 다음 Base에서 시작해 이름마다 하나의 데코레이터로 순서대로 감싸는 build(names)를 작성하세요(cheese, olives 또는 double).

제공된 코드는 쉼표로 구분된 이름이 포함된 줄 또는 plain을 읽고, 각 피자를 구성한 다음 1050: cheese, olives 또는 800: plain을 출력합니다.

코드는 Base.kt, Cheese.kt, Olives.kt, DoubleUp.kt, Build.kt 및 Pizza.kt에 작성하세요. Main.kt에는 제공된 입력/출력 코드가 포함되어 있으며 수정할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입력/출력 코드: 그대로 유지하세요
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val names = if (line == "plain") listOf() else line.split(",")
        val pizza = build(names)
        val toppings = pizza.toppings()
        println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
    }
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: Kotlin 플레이그라운드