-
(3) Decorator Pattern프로그래밍/Design Pattern 2023. 3. 13. 00:42
여러 종류의 음료 가격을 계산하는 부분을 구현하려고 합니다.
음료별 종류뿐만 아니라 커피처럼 토핑 옵션이 있을 때, 음료 메뉴들의 부모클래스를 선언하고 각 메뉴별로 상속하여 옵션별로 가격을 반환하는 구조로 만들 수 있습니다.
class Beverage { func cost() -> Int { return 2500 } } class Espresso: Beverage { override func cost() -> Int { return super.cost() } } class TwoShotEspresso: Beverage { override func cost() -> Int { return super.cost() + 1000 } } class ThreeShotEspresso: Beverage { override func cost() -> Int { return super.cost() + 1000 * 2 } }
위 코드는 극단적으로 모든 옵션별로 클래스를 나눈 경우입니다. 클래스를 어떤 카테고리로 나누는가에 따라서 위처럼만큼은 나누지 않을 수 있겠지만, 결국 음료별로 클래스를 나누게 될 경우 옵션 가격을 각 서브클래스에서 직접 계산하여 구현하는 방향으로 구현하게 됩니다. 계산해서 구현하는 방법이 꼭 나쁜 것은 아니지만 경우를 애매하게 나누기 어려운 경우가 발생하게 되었을 때 일관성 없게 코드를 작성하게 될 수도 있습니다. 위 경우 커피 음료에 대해서 기본 가격인 2500원을 적용하려고 기본 Cost를 적용했지만, 일반 물 메뉴인 경우 위 기본 가격을 같이 적용하기는 어렵습니다.
두번째 방법으로는 슈퍼 클래스에서 가능한 옵션들에 대해서 미리 만들어두고, 서브 클래스에서 이를 조합하여 사용하는 방법이 있습니다.
class Beverage { func cost() -> Int func setCoffee() func setMilk() func setWhip() func setMocha() ... }
이 경우 각 개별 음료 클래스에서 슈퍼 클래스의 함수를 조합하여 가격을 구성할 수 있습니다.
이 경우의 문제는 무엇일까요?
신메뉴가 추가 될 때 신메뉴 서브클래스가 추가됨과 동시에 슈퍼클래스인 Beverage도 같이 수정이 일어난다는 것 입니다.
만약 위 코드에서 녹차메뉴가 추가되었다면 setTea() 와 같은 함수가 추가되어야 서브클래스에서 사용 할 수 있게 됩니다.
Decorator 패턴은 객체에 추가 요소를 동적으로 추가(장식)할 수 있게 하는 패턴입니다.
위에서 설명한 예시에서 2개의 샷이 들어간 에스프레소의 경우,
((((음료) + 커피 )+ 샷) + 샷) 으로 표현하여 해결한다는 구조를 갖습니다.
코드로 설명하면 아래와 같습니다.
class Beverage { func cost() -> Int { return 500 } } class BeverageDecorator: Beverage { } class Shot: BeverageDecorator { var beverage: Beverage init(beverage: Beverage) { self.beverage = beverage } override func cost() -> Int { return 1000 + self.beverage.cost() } } class Whip: BeverageDecorator { var beverage: Beverage init(beverage: Beverage) { self.beverage = beverage } override func cost() -> Int { return 500 + self.beverage.cost() } } class Tea: BeverageDecorator { var beverage: Beverage init(beverage: Beverage) { self.beverage = beverage } override func cost() -> Int { return 1000 + self.beverage.cost() } } var beverage = Beverage() var whipBeverage = Whip(beverage) var shotAndWhipBeverage = Shot(whipBeverage) // Shot(Whip(beverage))
위 코드에서는 음료를 꾸미는 Decorator 클래스가 있습니다. 이 클래스는 Beverage 클래스(Component)에 대한 참조를 가지고 있고, 이 인스턴스의 인터페이스 cost를 호출하는 구조를 가지고 있습니다.
만약 새로운 메뉴가 출시가 된다면 기존 Beverage 클래스를 수정할 필요없이 새로운 Decorator를 선언하여 사용하면 됩니다.
확장에 대해서 열려있고 변경에 대해서 닫혀있는 OCP(Open-Closed Principle)원칙을 준수하고 있음을 확인할 수 있습니다.
반응형'프로그래밍 > Design Pattern' 카테고리의 다른 글
(2) Observer Pattern (0) 2023.03.05 (1) Strategy Pattern (0) 2023.03.05