ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [OOP] 디자인 패턴 - Decorator Pattern(데코레이터 패턴)
    프로그래밍 2024. 7. 17. 21:52

    ## 데코레이터 패턴(Decorator Pattern).

    객체에 추가적인 요건을 동적으로 첨가한다. 데코레이터는 서브클래스를 만드는 것을 통해기능을 유연하게 확장할 수 있는 방법을 제공한다.

     

     

    ## 커피 클래스를 구현해보자 - (1)

    우리가 수 많은 커피를 클래스로 구현한다고 했을때,

    일반적으로 아래와 같은 구조를 생각하기 쉽다.

    나 또한 가장 먼저 이런 생각이 떠올랐었고,

    회사에 다니는 젊은 개발자들에게 물어봤을때도 위와 같은 구조를 우선적으로 많이 말씀하셨다.

     

    하지만 조금만 더 고민해보면 이런 구조는 문제가 있는걸 알 수 있다.

     

    커피는 너~무 많다.

    수십, 수백가지 되는 커피들이 있고,

    커피콩은 어떤것을 쓸 것이고

    두유를 첨가할 지 우유를 첨가할 지,

    샷은 얼마나 추가할 것인지,

    휘핑크림 넣고 빼고,

    이런것들을 고려해야 하며, 

    해당 커피들을 모두 상속으로 처리하면 클래스가 너무 많아지고 관리하기 힘들어질 것이다.

     

    ## 커피 클래스를 구현해보자 - (2)

    그렇다면 생각을 조금 바꿔서, 샷, 두유 등의 속성을 변수로 가져와 아래와 같은 구조로 클래스를 구현하면 어떨까?

    위와 같은 형태가 된다 해도 여전히 문제가 있다.

    ### 문제점 1 - 첨가물의 종류가 추가되거나 변경되면 서브클래스에 영향이 갈 수 있음

    "여름 한정 신메뉴가 추가되었습니다. 당분간 레몬시럽을 첨가한 커피를 팔거에요."

     

    첨가물의 종류가 추가되거나 변경된다면? Beverage 클래스에 변수를 수정할 것이고,

    그것을 상속받고 있는 서브클래스들에게 영향이 갈 수 있다.

    ### 문제점 2 - 필요없는 변수를 가지고 있는 서브클래스들이 있음.

    특정 첨가물이 필요없는 서브클래스가 있을 수 있다.

    예를들어 IceTea 클래스는 Beverage이지만,  milk ,  whip ,  mocha  등이 필요없다.

     

    ## 커피 클래스를 변경해보자 - 데코레이터 패턴

    • Beverage 클래스를 상속받은 CondimentDecorator 클래스가 생겼다.
      상속을 쓰지만, 형식을 맞추기 위한 것일 뿐, 상속을 통해 행동을 물려받는 목적이 아니다.
    • 데코레이터를 통해 행동이 추가되는 것이다.
    class Expresso: Beverage {
        var description: String = "에스프레소 커피"
        
        func cost() -> Float {
            return 2000
        }
    }
    class DarkRoast: Beverage {
        var description: String = "다크로스팅 커피"
        
        func cost() -> Float {
            return 3000
        }
    }

     

    protocol CondimentDecorator: Beverage {
        var beverage: Beverage {get set}
    }
    
    class Soy: CondimentDecorator {
        var beverage: Beverage
        var description: String
        
        init(beverage: Beverage) {
            self.description = beverage.description + ", 두유"
            self.beverage = beverage
        }
        
        func cost() -> Float {
            return beverage.cost() + 200
        }
    }
    
    class Whip: CondimentDecorator {
        var beverage: Beverage
        var description: String
        
        init(beverage: Beverage) {
            self.description = beverage.description + ", 휘핑"
            self.beverage = beverage
        }
        
        func cost() -> Float {
            return beverage.cost() + 120
        }
    }

     

    위와 같이 첨가물(CondimentDecorator)와 그 서브클래스들(Soy, Whip, Mocha..) 등을 만들면 준비는 끝났다.

     

    이제 커피를 만들때에도,

    var drink: Beverage = DarkRoast() // 다크 로스팅된 커피 생성
    drink = Soy(beverage drink) // 두유 추가
    drink = Whip(beverage drink) // 휘핑 크림 추가

    이런 식으로 다크 로스트 원두 커피에, 두유와 휘핑크림을 추가하는(decorate 하는) 형태로서

    동적으로 클래스를 변경하는게 가능하다.

     

    ## 정리

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

     

    • 여기서는 CondimentDecorator가 Beverage를 상속받음으로서 형식을 맞춰 기능을 유연하게 확장할 수 있는 방법을 제공했다.
    • 메소드 체이닝과 같이 생각해보면 좋을 것 같다. 만약 커피 만들기를 메소드체이닝으로 하면 어땠을까?

     

    ## Ref

    - Head First Design Patterns

     

     

Designed by Tistory.