본문 바로가기

[Unity] 디자인 패턴 : 데코레이터 패턴(Decorator Pattern)

앤디가이 2022. 6. 21.

Unity와 C#을 통한 데코레이터 패턴(Decorator Pattern)의 정의와 사용 방법에 대해 알아보자.

 

1. 데코레이터 패턴(Decorator Pattern)이란?

데코레이터 패턴(Decorator Pattern)은 객체에 추가 요소를 동적으로 더할 수 있는 디자인 패턴이다. 개발 확장에 있어 base 클래스 밑에 서브 클래스를 만들어 확장시키는 방법을 주로 사용하는데 데코레이터를 사용하면 서브 클래스를 만들어 확장할 때보다 더 확장성 있게 구현할 수 있다.

데코레이터 패턴을 이해하기 위해서는 디자인 원칙 중 OCP(Open-Closed Principle) 원칙에 대해 알고있으면 좋다.

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

데코레이터 패턴은 이런 OCP 원칙에 따라 구성된 패턴이다.

좋은 유지보수를 위해 개발은 기존 코드를 최대한 건드리지 않고 확장을 통해서 새로운 행동을 추가할 수 있게 디자인 하는 것이 좋다. OCP 원칙을 잘 따르면, 새로운 기능 추가에 있어서 유연하고(확장에 열려있음) 튼튼한(변경에는 닫혀있음) 설계를 구현할 수 있다. 

 

2. 데코레이터 패턴(Decorator Pattern)은 어디에 활용하면 좋을까? 

데코레이터란 말만 보면, 객체를 꾸미거나, 첨가할때 쓰면 좋을 것 같다는 생각이 든다.

실제로 주요 예제들을 보면 다음과 같은 예제가 있을 것 같다.

1. 케익 만들기 - 케이크에 들어가는 각종 데코레이션 요소(생크림 추가, 초콜릿 추가, 과일 추가 등)들을 추가하여 케이크 객체를 만드는 예

2. 캐릭터 생성 - 캐릭터 요소별 아이템 장착(머리띠, 수건, 모자, 안경 등)으로 캐릭터를 꾸미며 캐릭터 객체를 만드는 예

3. 커피 주문 - 커피 주문 시스템에서 각종 추가 요소를 주문받아(휘핑크림, 바닐라시럽, 우유 추가 등) 커피 객체를 생성하는 예

4. 자동차 주문 - 자동차 주문시 각종 옵션 요소를 주문받아(LED 추가, 열선 시트 추가 등) 자동차 객체를 생성하는 예

아직까지 유니티에서 잘 활용되고 있는 사례는 모르겠다.(빌더 패턴이 있기 때문이 아닐까 하는 생각이 듬)

3. 데코레이터 패턴(Decorator Pattern) 사용 예제

자동차 주문 시스템 예제를 통해 유니티에서 데코레이터 사용 예제에 대해 알아보자.

 

시스템 명칭 : 자동차 주문 시스템 개발

자동차 주요 추가 옵션 : LED 헤드라이트, 썬루프(Sunroof), 어댑티브 크루즈 컨트롤(ACC), 첨단 운전자 보조 시스템(ADAS) 등

시스템 목표 : 옵션을 추가한 자동차의 최종 가격 산출

 

위와 같은 예제로 클래스를 구성해보자. 물론 다른 디자인 패턴이나 구현 방법이 있을 수 있으나, 여기서는 데코레이터 패턴을 통해서 간단히 샘플을 만들어 보자.

 

자동차 클래스의 구조도를 그려보면 아래와 같이 구성할 수 있다.

데코레이터 패턴 구조도 예제
데코레이터 패턴 구조도 예제

 CarDecorator를 통해 각 옵션 등을 추가할 수 있게 감싸주는 걸 볼 수 있다.

 

자동차 base 클래스와 K5, K7  등 자동차 종류별 서브 클래스는 다음과 같이 클래스를 구성해 볼 수 있다.

namespace DecoratorPattern
{
    // 자동차 추상클래스
    public abstract class Car
    {
        //자동차 설명
        protected string description = string.Empty;
        public abstract string GetDescription();

        //자동차 비용
        public abstract int Cost();
    }

    public class K5 : Car
    {
        public K5()
        {
            this.description = "K5 자동차";
        }
        public override int Cost()
        {
            //K5의 비용을 리턴.
            return 10000;
        }

        public override string GetDescription()
        {
            return this.description;
        }
    }
}

 

자동차 옵션을 데코레이션 해줄 CarDecorator 클래스는 다음과 같이 구성해 볼 수 있다.

namespace DecoratorPattern
{
    public abstract class CarDecorator : Car
    {
        protected Car car;
    }
}

CarDecorator를 상속받는 옵션들을 서브 클래스로 구현하면 다음과 같다.

namespace DecoratorPattern
{
    public class LedHeadLamp : CarDecorator
    {
        //생성자
        public LedHeadLamp(Car car)
        {
            this.car = car;
        }

        public override int Cost()
        {
            //자동차 가격에 Led해드램프를 추가한 가격을 리턴한다.
            return car.Cost() + 2000;
        }

        public override string GetDescription()
        {
            //자동차 설명에 옵션 추가 내역을 추가한 후 리턴한다.
            return car.GetDescription() + " ,LED해드램프 추가됨";
        }
    }

    public class Sunroof : CarDecorator
    {
        //생성자
        public Sunroof(Car car)
        {
            this.car = car;
        }

        public override int Cost()
        {
            //자동차 가격에 썬루프를 추가한 가격을 리턴한다.
            return car.Cost() + 5000;
        }

        public override string GetDescription()
        {
            //자동차 설명에 옵션 추가 내역을 추가한 후 리턴한다.
            return car.GetDescription() + " ,썬루프 추가됨";
        }
    }
}

 

이제 테스트 코드를 작성해보자.

CarSimulator.cs 클래스를 만든 후 Scene GameObject에 Attach 한 후 실행해보자.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace DecoratorPattern
{
    public class CarSimulator : MonoBehaviour
    {
        void Start()
        {
            Car myCar = new K5();
            Debug.Log(myCar.GetDescription());

            myCar = new LedHeadLamp(myCar); //해드램프 데코레이터 추가
            myCar = new Sunroof(myCar); // 썬루프 데코레이터 추가

            string result = string.Format("자동차 총 가격은 : {0}, 자동차 전체 구성 : {1}", myCar.Cost(), myCar.GetDescription());
            Debug.Log(result);
        }
    }
}

 

K5 자동차 객체를 생성한 후, 해드 램프와 썬루프 옵션 객체를 추가해준다.

객체는 생성 시 본인 Car 객체를 리턴하기 때문에 옵션이 추가될수록 가격이 더해지고, Description도 추가된다.

결과를 보면 다음과 같다.

 

유니티 데코레이터 패턴 사용 예
결과 화면

K5 자동차 객체가 생성된 후 초기 Cost(비용)은 10,000원이었다.

new LedHeadlamp()를 통해 해드램프를 추가를 해주니 가격은 12,000원으로 올라가고, Description도 추가됐다.

new Sunroof()를 통해 썬루프를 추가하니 마지막 총가격은 17,000원으로 올라가고 Description도 추가된 걸 확인할 수 있다.

 

4. 정리

데코레이터 패턴을 통해 기능을 확장하는 법을 배웠다.

위 예제처럼 자동차 옵션을 확장해 나갈 때 베이스 코드는 수정 없이(변경에는 닫혀있음) 구성되고, 확장 코드는 유연하게 구성된 OCP 원칙이 지켜진 구성을 살펴볼 수 있다.

데코레이터 패턴의 단점은 확장이 몇 개 안 되는 구성에 있어서는 적용 시 코드 양과 스크립트 수량이 늘어나는 단점도 존재한다.

 

데코레이터 패턴과 유사하면서 좀 더 나은 방법을 제공하는 패턴이 있는데 바로 빌더 패턴이다. 빌더 패턴도 자기 본인 객체를 리턴함으로 데코레이터 패턴과 마찬가지로 옵션에 대한 확장을 유연하게 구성할 수 있다. 빌더 패턴은 추후 빌더 패턴 포스팅에서 자세히 설명하도록 하겠다.

 

댓글