HaPpY hApPy

DecoratorPattern 데코레이터 패턴 본문

DesignPattern (디자인패턴)/Head First Design Pattern

DecoratorPattern 데코레이터 패턴

juniguya 2013. 7. 27. 13:11

이 패턴을 이용한 예제는 두가지가 있었다,


한가지는 유명한 커피샵의 커피 주문,

(눈여겨 볼부분은, 커피 종류 와, 사이즈에 따른 가격, 그리고 추가되는 샷이나 휘핑 같은걸 어떻게 가격을 매기는지)


다른 한가지는 게임에서 데미지 입었을때,

(데미지를 받앗을때 보호막이 있는지 없는지, 보호막이 있을경우는 데미자가 100%, 보호막이 없을경우 데미지 50%, 마치 스타 프로토스 같이)


Head First Design Pattern 에는 커피 주문을 예로 되어있었다.


내가 까먹을경우

"첨가물과, 구상 구성요소의 차이점을 기억할것!"

"첨가물은 여러번 들어갈수 있지만, 구상 구성요소는 한번만 들어간다!!"


DecoratorPattern.zip

먼저 커피 사이즈 Enum BeverageSize이다.

namespace DecoratorPattern
{
    public enum BeverageSize
    {
        TALL,
        GRANDE,
        VENTI
    }
}

먼저 추상 구성요소 Beverage 클래스

namespace DecoratorPattern
{
    public abstract class Beverage
    {
        protected string description = "Unknown Beverage"; //추상 클래스다, 이 클래스가 객체화 될 일은 없다,

        BeverageSize size; //Size enum 이다,..

        public BeverageSize Size { get { return size; } set { size = value; } }
        
        public virtual string getDescription() //자바에서는 무조건 다 virtual 라고 수업시간에 들었던거 같다,,,잘 모르겠다 여튼 c#이랑 c++ 에서는 virtual 라고적어줘야된다.
        {
            return description;
        }

        public abstract double cost(); //가격
    }
}


그리고 이를 상속하는 추상 데코레이터(휘핑, 모카, 스팀우유, 두유 가 상속할 클래스)

namespace DecoratorPattern
{
    public abstract class CondimentDecorator : Beverage
    {
        public CondimentDecorator() //자바와 c#의 문법 차이인듯...-_-;
        {
            //public abstract string getDescription(); 자바에는 이렇게 되어있지만 c#에서는 이부분 지우고 아무것도 하는게 없는 클래스를 상속받는다,,-_-;;문법 차이인듯
        }
    }
}

위에 사실 클래스는,,,하는게 없다,,

아무래도 c#과 java 문법에서 오는 차이 때문에,,,어쩔수 없이 넣은듯  하다,

자바에서는 넣은 이유가 하위 클래스들이 꼭! getDescription() 함수를 오버라이드 하게 하기위해 abstract 으로 선언되어있다.

나중에 이를 상속받을(Soy, Whip, Mocha, SteamMilk 클래스가 바로 Beverage 를 상속해도 작동은 한다,,)


그리고 구상 구성요소 (에스프레소, 하우스블렌드, 다크 로스트, 디카프) 클래스들

namespace DecoratorPattern
{
     public class Espresso : Beverage //현재 에스프레소 에만 사이즈 를 추가했다. 밑에 나머지 3개 클래스는 이거보고 따라하면된다.
    {
        public Espresso(BeverageSize size)
        {
            Size = size;
            description = "에스프레소";
        }

        public override string getDescription()
        {
            switch (Size)
            {
                case BeverageSize.TALL:
                    return description + " TALL";

                case BeverageSize.GRANDE:
                    return description + " GRANDE";

                case BeverageSize.VENTI:
                    return description + " VENTI";

                default:
                    return description;
            }
        }

        private double GetSize(BeverageSize size)
        {
            switch (size)
            {
                case BeverageSize.TALL:
                    return 1.00;

                case BeverageSize.GRANDE:
                    return 1.55;

                case BeverageSize.VENTI:
                    return 2.10;

                default:
                    return 1.99;
            }
        }

        public override double cost()
        {
            return GetSize(base.Size);
        }
    }

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

        public override string getDescription()
        {
            return description;
        }

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

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

        public override string getDescription()
        {
            return description;
        }

        public override double cost()
        {
            return .99;
        }
    }

    public class Decaf : Beverage
    {
        public Decaf()
        {
            description = "디 카프";
        }

        public override string getDescription()
        {
            return description;
        }

        public override double cost()
        {
            return 1.05;
        }
    }
}

눈여겨 봐야할것은 구상 구성요소 (에스프레소, 하우스블렌드, 다크 로스트, 디카프)들은 바로 슈퍼 클래스 Beverage를 상속받는다.


그리고 구상 데코레이터들(휘핑, 모카, 우유, 두유)

namespace DecoratorPattern
{
    public class Mocha : CondimentDecorator //구상 데코레이터 들중에서는 모카에만 사이즈 별로 요금및 이름이 다르게 나오게 했다. 나머지 3개도 이처럼 하면됨
    {
        Beverage _beverage;
        public Mocha(Beverage beverage, BeverageSize size)
        {
            description = ", 모카";
            _beverage = beverage;
            Size = size;
        }

        public override string getDescription()
        {
            switch (Size)
            {
                case BeverageSize.TALL:
                    return _beverage.getDescription() +  description + "(TALL)";

                case BeverageSize.GRANDE:
                    return _beverage.getDescription() + description + "(GRANDE)";

                case BeverageSize.VENTI:
                    return _beverage.getDescription() + description + "(VENTI)";

                default:
                    return _beverage.getDescription();
            }
        }

        public override double cost()
        {
            switch (Size)
            {
                case BeverageSize.TALL:
                    return .10 + _beverage.cost();

                case BeverageSize.GRANDE:
                    return .15 + _beverage.cost();

                case BeverageSize.VENTI:
                    return .30 + _beverage.cost();

                default:
                    return .20 + _beverage.cost();
            }
           
        }
    }

    public class Soy : CondimentDecorator
    {
        Beverage _beverage;
        public Soy(Beverage beverage)
        {
            _beverage = beverage;
        }

        public override string getDescription()
        {
            return _beverage.getDescription() + ", 두유";
        }

        public override double cost()
        {
            return .20 + _beverage.cost();
        }
    }

    public class Whip : CondimentDecorator
    {
        Beverage _beverage;
        public Whip(Beverage beverage)
        {
            _beverage = beverage;
        }

        public override string getDescription()
        {
            return _beverage.getDescription() + ", 휘핑";
        }

        public override double cost()
        {
            return .20 + _beverage.cost();
        }
    }

    public class SteamMilk : CondimentDecorator
    {
        Beverage _beverage;
        public SteamMilk(Beverage beverage)
        {
            _beverage = beverage;
        }

        public override string getDescription()
        {
            return _beverage.getDescription() + ", 스팀 밀크";
        }

        public override double cost()
        {
            return .20 + _beverage.cost();
        }
    }
}

이들은 추상 데코레이터(CondimentDecorator)를 상속받는다.


마지막으로 이를 실행하는 메인함수.

namespace DecoratorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            Beverage beverage = new Espresso(BeverageSize.TALL); //그냥 에스프레스 톨사이즈
            Console.WriteLine(beverage.getDescription() + " $" + beverage.cost());

            Beverage beverage2 = new Espresso(BeverageSize.GRANDE);//에스프레스 그란데 사이즈
            beverage2 = new Mocha(beverage2, BeverageSize.TALL); // 모카 톨 사이즈 추가
            beverage2 = new Mocha(beverage2, BeverageSize.GRANDE); // 모잘라서 그란데 다시 추가
            beverage2 = new Whip(beverage2); //휩핑 올려서
            Console.WriteLine(beverage2.getDescription()+ " $" + beverage2.cost());//가격

            Beverage beverage3 = new Espresso(BeverageSize.VENTI);//이번엔 벤티 사이즈
            beverage3 = new Soy(beverage3);//두유
            beverage3 = new Whip(beverage3);//휘핑
            beverage3 = new SteamMilk(beverage3);//스팀밀크 
            beverage3 = new Mocha(beverage3, BeverageSize.GRANDE);//모카 그란데까지
            Console.WriteLine(beverage3.getDescription() + " $" + beverage3.cost());
        }
    }
}