본문 바로가기

디자인패턴

[디자인패턴] Chapter 03. 데코레이터 패턴

데코레이터 패턴 (Decorator Pattern)

기존 클래스 코드를 바꾸지 않고도 객체에 추가 요소를 동적으로 더할 수 있다.

기능의 유연한 확장을 위해 상속대신 객체를 장식(decorator)한다.

 

 

디자인 원칙

OCP (Open-Closed Principle)

: 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다.

=> 기존 코드는 건드리지 않고(Closed), 새로운 기능을 추가(Open)할 수 있어야 한다.

 

※ 무조건 OCP를 적용한다면 시간을 낭비할 수 있으며, 추상화를 하다 보면 필요 이상으로 복잡하고 이해하기 힘든 코드를 만들게 될 수 있다.

가장 바뀔 가능성이 높은 부분을 중점적으로 살펴보고 OCP를 적용하는 것이 좋다.

 

 

초대형 커피 전문점, 스타버즈

- 다양한 음료를 모두 포괄하는 주문 시스템 구축

 

방안1.  음료 종류마다 서브클래스로 구현하여 각 음료의 가격을 cost에서 리턴

문제점1.  우유, 두유, 샷추가 등 커스텀 주문이 들어오는 경우에는?

- 위와 같이 음료 종류마다 서브클래스로 구현하게 되면, 커스텀 주문을 하는 모든 경우의 클래스를 생성해야 한다.

ex) class EspressoWithSteamedMilk, class DecafWithSoy...

 

 

방안2.  각 첨가물에 해당하는 변수를 추가해보자.

public class Beverage
    {
        // 변수 및 메소드

        public abstract double cost()
        {
            double totalCost = 0;
            if (hasMilk())
                totalCost += 500;
            if (hasSoy())
                totalCost += 600;
            if (hasMocha())
                totalCost += 550;
            if (hasWhip())
                totalCost += 300;
                
            return totalCost;
        }
    }

    public class DarkRoast : Beverage
    {
        public DarkRoast()
        {
            description = "최고의 다크 로스트 커피";
        }

        public override double cost()
        {
            return 3000 + base.cost();
        }
    }

문제점1. 첨가물 가격이 바뀌는 경우?

- 바뀔 때마다 기존 코드를 수정해야 한다.

 

문제점2. 첨가물의 종류가 많아지면?

- 종류가 추가될 때마다 새로운 변수, 메소드를 추가해야 하고 cost() 메소드도 수정해야 한다.

 

문제점3. 티 음료 출시?

- 모든 음료는 Beverage를 상속받으므로, 우유, 두유, 모카, 휘핑크림이 들어가지 않는 티 음료도 모든 변수와 메소드를 상속받게 된다.

 

방안3.  데코레이터 패턴 적용하기

1) DarkRoast 객체를 가져온다.

2) Mocha 객체로 장식한다.

3) Whip 객체로 장식한다.

4) cost() 메소드를 호출한다.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DesignPattern.DecoratorPattern
{
    public abstract class Beverage
    {
        protected string description = "";

        public virtual string GetDescription()
        {
            return description;
        }

        public abstract double Cost();
    }

    public class Espresso : Beverage
    {
        public Espresso()
        {
            this.description = "에스프레소";
        }

        public override double Cost()
        {
            return 1.99;
        }
    }

    public class HouseBlend : Beverage
    {
        public HouseBlend()
        {
            this.description = "하우스 블렌드 커피";
        }

        public override double Cost()
        {
            return .89;
        }
    }

    public class DarkRoast : Beverage
    {
        public DarkRoast()
        {
            this.description = "다크 로스트 커피";
        }

        public override double Cost()
        {
            return .90;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DesignPattern.DecoratorPattern
{
    public abstract class CondimentDecorator : Beverage
    {
        protected Beverage beverage;

        public CondimentDecorator(Beverage beverage)
        {
            this.beverage = beverage;
        }
    }

    public class Mocha : CondimentDecorator
    {
        public Mocha(Beverage beverage) : base(beverage) { }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ", 모카";
        }

        public override double Cost()
        {
            return beverage.Cost() + .20;
        }
    }

    public class Whip : CondimentDecorator
    {
        public Whip(Beverage beverage) : base(beverage) { }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ", 휘핑크림";
        }

        public override double Cost()
        {
            return beverage.Cost() + .14;
        }
    }

    public class Soy : CondimentDecorator
    {
        public Soy(Beverage beverage) : base(beverage) { }

        public override string GetDescription()
        {
            return beverage.GetDescription() + ", 두유";
        }

        public override double Cost()
        {
            return beverage.Cost() + .3;
        }
    }
}