5. Decorator Pattern ( 데코레이터 / 장식자 패턴 C# )

|

[읽기전에]

  UML 다이어그램 : UML클래스 다이어그램 기본상식 http://hongjinhyeon.tistory.com/25 

  포스팅되는 디자인 패턴의 예는 스타크래프트를 기본으로 하였습니다 : 디자인 패턴을 시작하며 http://hongjinhyeon.tistory.com/24



<기본 정의 >


1. Adapter Pattern 정의

 

  -객체에 추가적인 요건을 동적으로 첨가한다.

   데코레이터는 서브클래스를 만드는 것을 통해서 기능을 유연하게 확장할 수 있는 방법을 제공한다.


  (Attach additional responsibilities to an object dynamically.

   Decorators provide a flexible alternative to subclassing for extending functionality. )


 -어셈블리 타임이 아니라 런타임에 기능을 동적으로 추가가 가능하게 만들 수 있다.



2. UML Diagram


 



3. 사용용도 및 장점


 -클래스의 기능들이 동적으로 변경될 소지가 있을때.


 -클래스에 동적으로 추가하는 기능들이 앞으로 더 추가될 소지가 있을때 ( 기존기능에 추가로  새로운 기능이 추가가 될 소지가 있을때 )


 -클래스에 기능들을 여러개로 조합이 될때 미리 정의해두면 너무 많은 클래스가 생성이 되므로, 즉시 즉시 생성이 가능하게 할때.


 -클래스에 새로운 기능을 추가할때 보통 변수나 메소드 들이 추가가 된다. 그럴때마다 추상 클래스나 해당객체에 소스 변경이 있을 수

  밖에 없다. 이것을 없앨 수가 있으며, 단순히 새로운 기능의 클래스만 추가되고 클라이언트 부분에서 기능만 추가만 해주면 된다.



4. 소스

using System;
 
namespace DoFactory.GangOfFour.Decorator.Structural
{
  /// <summary>
  /// MainApp startup class for Structural
  /// Decorator Design Pattern.
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Entry point into console application.
    /// </summary>
    static void Main()
    {
      // Create ConcreteComponent and two Decorators
      ConcreteComponent c = new ConcreteComponent();
      ConcreteDecoratorA d1 = new ConcreteDecoratorA();
      ConcreteDecoratorB d2 = new ConcreteDecoratorB();
 
      // Link decorators
      d1.SetComponent(c);
      d2.SetComponent(d1);
 
      d2.Operation();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// <summary>
  /// The 'Component' abstract class
  /// </summary>
  abstract class Component
  {
    public abstract void Operation();
  }
 
  /// <summary>
  /// The 'ConcreteComponent' class
  /// </summary>
  class ConcreteComponent : Component
  {
    public override void Operation()
    {
      Console.WriteLine("ConcreteComponent.Operation()");
    }
  }
 
  /// <summary>
  /// The 'Decorator' abstract class
  /// </summary>
  abstract class Decorator : Component
  {
    protected Component component;
 
    public void SetComponent(Component component)
    {
      this.component = component;
    }
 
    public override void Operation()
    {
      if (component != null)
      {
        component.Operation();
      }
    }
  }
 
  /// <summary>
  /// The 'ConcreteDecoratorA' class
  /// </summary>
  class ConcreteDecoratorA : Decorator
  {
    public override void Operation()
    {
      base.Operation();
      Console.WriteLine("ConcreteDecoratorA.Operation()");
    }
  }
 
  /// <summary>
  /// The 'ConcreteDecoratorB' class
  /// </summary>
  class ConcreteDecoratorB : Decorator
  {
    public override void Operation()
    {
      base.Operation();
      AddedBehavior();
      Console.WriteLine("ConcreteDecoratorB.Operation()");
    }
 
    void AddedBehavior()
    {
    }
  }
}


5. 실행결과


 ConcreteComponent.Operation()

 ConcreteDecoratorA.Operation()

 ConcreteDecoratorB.Operation()




< 실제 적용 >


1. UML Diagram






2. 사용용도 및 장점


 -데코레이터 패턴이 스타크래프트에서 어떻게 적용되는지 고민을 많이했다. 생각보다 쓰이는 위치가 많지는 않은거 같다.

  (좀 억지스러운 면도 있을 수 있겠지만 그냥 장식자를 설명할 꺼리로 생각해주면... ^^)


 -테란 유닛중에 사이언스 배슬이라는 유닛은 아군에게 방어막(Defensive Matrix)를 걸어 줄 수가있다. 이것이 활성화된 유닛은

  적의 공격을 받으면 방어막의 체력만큼 데미지를 일정기간동안 흡수가된다. 방어막의 공격력을 넘으면 본체의 체력이 줄어 들게

  된다. 이런 경우에 사용이 가능하다.


 -유닛에게 효과가 적용되는 기술이나 기능이 추가될때는,  UnitDecorator를 상속받는 클래스를 생성해주고 신규 기술을 생성해주고

  유닛에게 기술을 적용만 해주면 된다.


 

3. 소스


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

namespace Decorator
{
    class Program
    {
        static void Main(string[] args)
        {

            // Create ConcreteComponent and two Decorators
            Marine marine = new Marine();
            DefensiveMatrix defensiveMatrix= new DefensiveMatrix();

            // Link decorators
            defensiveMatrix.SetComponent(marine);

            defensiveMatrix.UnderAttack(50);
            defensiveMatrix.UnderAttack(50);
            defensiveMatrix.UnderAttack(50);

            // Wait for user
            Console.ReadKey();
        }

        /// <summary>
        /// 콤포넌트 클래스 
        /// </summary>
        abstract class Unit
        {
            public int health = 100;
            public abstract void UnderAttack(int _Damage);
        }

        /// <summary>
        /// ConcreteComponent 클래스
        /// </summary>
        class Marine : Unit
        {
            public override void UnderAttack(int _Damage)
            {
                health -= _Damage;
                Console.WriteLine("남은 체력 : " + health.ToString() + ", 받은 데미지 : " + _Damage.ToString());
            }
        }

        /// <summary>
        /// 데코레이터 클래스
        /// </summary>
        abstract class UnitDecorator : Unit
        {
            protected Unit component;

            public void SetComponent(Unit component)
            {
                this.component = component;
            }

            public override void UnderAttack(int _Damage)
            {
                if (component != null)
                {
                    component.UnderAttack(_Damage);
                }
            }
        }
               
        /// <summary>
        /// ConcreteDecorator 클래스 
        /// </summary>
        class DefensiveMatrix : UnitDecorator
        {
            private int addedHealth = 100;
            private int damage = 0;

            public override void UnderAttack(int _Damage)
            {
                CheckDefensiveMatrix(_Damage);
                base.UnderAttack(damage);
            }

            void CheckDefensiveMatrix(int _Damage)
            {
                int remainHealth=addedHealth-_Damage;

                //addedHealth -= _Damage;

                if (remainHealth >= 0)
                {
                    //방어막이 데미지를 모두 흡수 했을때
                    addedHealth -= _Damage;
                    Console.WriteLine("보호막으로 데미지 " + _Damage.ToString() + "모두 흡수 ,남은 보호막 : " + remainHealth.ToString());
                    damage = 0;
                }
                else
                {
                    //방어막이 모두달았을때
                    if (addedHealth == 0)
                    {
                        //기존의 방어막이 없었을때
                        Console.WriteLine("보호막으로 흡수 못함, 남은 보호막 : 0");
                        damage = _Damage;
                    }
                    else
                    {
                        //기존의 방어막이 있는데 다 소멸되었을대

                        Console.WriteLine("보호막으로 데미지 " + (_Damage - addedHealth).ToString() + "만 흡수, , 남은 보호막 : 0");
                        damage = _Damage - addedHealth;
                        addedHealth = 0;
                    }
                 
                    
                }
               
            }
        }
    }
}


4. 실행결과





5. 피드백


 -데코레이터 패턴을 설명하는데 예를 들을때 보통 커피전문점을 든다. 커피종류에 토핑을 머로 하느냐에 따라서 다양한 커피종류와

  가격이 형성되므로 데코레이터 패턴을 적용하기에 알맞다. (Head First Desing Patterns에 잘 나와있다)


 -데코레이터 패턴은 기존 클래스에 새로운 기능이 많이 추가될 수가 있고, 종류가 다양할 때만 사용하는게 좋을거 같다. 너무 적은량이면

  그냥 고정적으로 하는것이 더 효율적으로 보인다. (큰 프로젝트가 아니라면 더더욱...)



[참조]

 -http://www.dofactory.com

And