Decorator Pattern

데코레이터 패턴(Decorator pattern)이란 주어진 상황 및 용도에 따라 어떤 객체에 책임을 덧붙이는 패턴으로, 기능 확장이 필요할 때 서브클래싱 대신 쓸 수 있는 유연한 대안이 될 수 있다.

최근 읽은 책에서 나온 패턴이였는데 Issue를 만들어두고 미루다 이제야 작성한다.

게임에서는 동작 관련 패턴이 가장 많이 사용되기도 하지만 상태, 전략과 같이 쓰면 좋은 패턴이라고 생각되어 정리한다.

간단하게 뱀서류 게임을 보면 전략 패턴과 데코레이터 패턴을 써서 제작하지 않았을까? 라는 생각을 종종 했는데 그거에 맞춰서 예제도 같이 만들어 봤다.

우선 뱀서는 기본 스킬에서 다른 스킬이 추가되고 해당 스킬들의 레벨을 올리면 더욱 다양해지는 형태이다.

사실 스킬 자체가 크게 변하지 않는다면 전략패턴이 맞는 것 같지만 레벨을 많이 올리면 스킬에 붙는 부과효과가 아이 다르다고 가정한다

예제

바로 예제를 먼저 보고 구조에 대해서 설명하는 순서로 진행

public interface ISkill
{
    void Use();
}
public class BaseSkill : ISkill
{
    public void Use()
    {
        System.Console.WriteLine("기본 스킬 사용");
    }
}
public abstract class SkillDecorator : ISkill
{
    private ISkill _skill;

    public SkillDecorator(ISkill skill)
    {
        _skill = skill;
    }

    public virtual void Use()
    {
        _skill.Use();
    }
}
public class LaserSkill : SkillDecorator
{
    public LaserSkill(ISkill skill) : base(skill)
    {
    }

    public override void Use()
    {
        base.Use();
        System.Console.WriteLine("레이저 스킬 사용");
    }
}
public class ComboSkill : SkillDecorator
{
    public ComboSkill(ISkill skill) : base(skill)
    {
    }

    public override void Use()
    {
        base.Use();
        System.Console.WriteLine("콤보 스킬 사용");
    }
}
public class Program
{
    public static void Main(string[] args)
    {
        ISkill baseSkill = new BaseSkill();
        baseSkill.Use();

        ISkill laserSkill = new LaserSkill(new BaseSkill());
        laserSkill.Use();

        ISkill comboSkill = new ComboSkill(new BaseSkill());
        comboSkill.Use();

        // ---

        ISkill totalSkill = new ComboSkill(new LaserSkill(new BaseSkill()));
        totalSkill.Use();
    }
}
기본 스킬 사용
기본 스킬 사용
레이저 스킬 사용
기본 스킬 사용
콤보 스킬 사용
기본 스킬 사용
레이저 스킬 사용
콤보 스킬 사용

코드 전문이긴 하지만 더 자세하게 보고 싶다면 Code Review 링크

설명

기본적인 기능에 추가(데코레이션)하는 다양한 종류의 추가적인 기능을 동적으로 조합할 수 있는 패턴이다.

ISkill 인터페이스는 스킬이 기본적으로 가져가는 인터페이스로 이를 DI로 주입받아서 사용하게 되는 추상클래스 SkillDecorator가 핵심이다.

이후 기본 스킬에 붙게 되는 Decorator들은 이 SkillDecorator를 상속받아서 구현하게 된다.

이 패턴을 사용하지 않는다면 이런 추가기능의 요구사항에 대해서 상속 구조를 복잡하게 가져가야 하기 때문에 유연성이 떨어지게 된다.

물론 지금 처럼 인터페이스를 제작해서 스킬안에 해당 인터페이스를 구성으로 들고 있게 할 수 있지만 해당 스킬에 대한 개수와 변경사항이 미지수이기 때문에 스킬 자체 클래스를 수정하게 되며 이는 SRP를 위반 그리고 나아가 해당 코드 자체의 변경이 많아지게 된다. (응집성 증가, 커플링 증가)

가장 좋은 점은 동적으로 행동 자체를 유연하게 추가할 수 있다는 점이다.

하지만 너무 많이 사용하게 되면 마찬가지로 복잡도가 많이 증가하게 될 수 있다.

지금 코드도 생성자 체이닝?을 통해 값을 할당하여 중간 객체는 중개자 정도로 사용되며 이는 코드 스멜이라고 생각된다.

그럼에도 매력적인 패턴이라 사용할 수 있을 땐 사용해도 좋을 것 같다.

댓글남기기