데코레이터 패턴

노지환·2022년 2월 8일
0

디자인패턴

목록 보기
2/2
post-custom-banner

객체에 추가적인 요건동적으로 첨가한다.

데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.

들어가기 전에,

상속은 is-a

구성은 has-a

클래스 다이어그램

Component

  • 구성요소를 나타내주는 클래스 or 인터페이스

Decorator

  • 자신기 장식할 구성요소가 같은 인터페이스 또는 추상 클래스를 구현.

ConcreteComponent

  • 새로운 행동을 동적으로 추가합니다.

ConcreteDecorators

  • 구상클래스 입니다!(== has-a)
  • Component 객체가 들어있습니다.(== 구성요소에 대한 레퍼런스가 들어있는 인스턴스 변수가 있는 것)
  • 이 객체를 통하여 추가적인 장식을 할 수 있습니다.

상속을 대신하기 위해 사용하는 패턴?

상속을 대체할 수는 없습니다.

다만 상속을 통해 하고자하는 일이 다릅니다!

데코레이터에서의 상속은

오버라이딩을 통하여 메소드의 행동을 변경하고 올바른(필요한) 형식을 맞추기 위해서 하는 것입니다!

객체의 구성(인스턴스 변수로 다른 객체를 저장하는 방식)을 통하여 유연성을 보장합니다!

데코레이터 패턴이 적용된 코드

Component Class

package decorator

abstract class Beverage(
    var description: String = "제목 없음"
) {
    open fun getDes() = description

    abstract fun cost(): Double
}

추상클래스 or 인터페이스 모두 가능.

ConcreteComponent

package decorator

class Espresso: Beverage() {

    init {
        description = "에스프레소"
    }

    override fun cost(): Double {
        return 1.99
    }
}

모든 첨가물 데코레이터에서 getDes()를 통하여 표현할 수 있습니다!

Decorator

package decorator

abstract class CondimentDecorator: Beverage() {
    abstract override fun getDes(): String
}

ConcreteDecorator

package decorator

class Mocha(
    private var beverage: Beverage
    ): CondimentDecorator() {

    override fun cost(): Double {
        return beverage.cost() + .20
    }

    override fun getDes(): String {
        return beverage.getDes() + ", 모카" // 재귀는 아님
    }
}
package decorator

class Whip(
    private var beverage: Beverage
    ) : CondimentDecorator() {

    override fun cost(): Double {
        return beverage.cost() + .59
    }

    override fun getDes(): String {
        return beverage.getDes() + ", 휘핑 크림"
    }
}

실행결과

package decorator

fun main() {
    val beverage = Espresso()
    println(beverage.description)

    var beverage2: Beverage = HouseBlend()
    beverage2 = Mocha(beverage2)
    beverage2 = Mocha(beverage2)
    beverage2 = Whip(beverage2)
    println(beverage2.getDes())
}

// 결과
// 에스프레소
// 하우스 블렌드 커피, 모카, 모카, 휘핑 크림
// 하우스 블렌드 커피, 모카, 휘핑 크림, 모카

패턴이 가진 단점

구상 구성요소(모카, 휘핑...)의 형식을 알아내서 결과를 바탕으로 어떤 작업을 처리하는 코드에는 적합하지 않습니다!

특정 형식에 의존하는 코드에 데코레이터를 그냥 적용해버리면 코드가 엉망이 됩니다!

그러다보니, 구성요소를 초기화하는 데 필요한 코드가 훨씬 복잡해집니다.

! 특정 형식에 의존하는 코드

→ 특정 데코레이터 내부에서 인스턴스 변수(Component component)가 참조하고 있는 구상 컴포넌트의 구현체를 알 수 없습니다.(Whip 클래스의 구성 클래스가 어떤 데코레이터로 감싸져 있는지 모른다는 뜻) 따라서, 데코레이터 수행 과정 중 구상 컴포넌트에 특정 작업을 할 수 없습니다!

단점을 극복하기 위한 패턴 사용

구성요소를 초기화하는데 필요한 코드가 훨씬 복잡해집니다.

→ 이러한 문제는 팩토리 패턴이나 빌더 패턴을 사용함으로써 해결이 가능합니다!

팩토리 메서드 패턴을 통한 코드 단순화

데코레이터 패턴의 Component가 Product로 들어가면서, 팩토리 메서드 패턴을 사용하였습니다!

코드로 작성해보면,

Creator

package decorator

abstract class BeverageStore {
    abstract fun createBeverage(type: String): Beverage
}

ConcreteCreator

package decorator

class StarbuzzStore: BeverageStore() {
    override fun createBeverage(type: String): Beverage {
        var targetBeverage: Beverage = Espresso()
        if(type == "HouseBlend") targetBeverage = HouseBlend()
        return targetBeverage
    }
}

결과

package decorator

fun main() {
    val starbuzzStore = StarbuzzStore()
    val beverage = starbuzzStore.createBeverage("Espresso")
    println(beverage.description)

    var beverage2 = starbuzzStore.createBeverage("HouseBlend")
    beverage2 = Mocha(beverage2)
    beverage2 = Mocha(beverage2)
    beverage2 = Whip(beverage2)
    println(beverage2.getDes())
}

// 결과
// 에스프레소
// 하우스 블렌드 커피, 모카, 모카, 휘핑 크림

이런식으로 사용이 가능할 것 같습니다!

profile
기초가 단단한 프로그래머 -ing
post-custom-banner

0개의 댓글