10. State Pattern ( 스테이트 / 상태 패턴 C# )

|

[읽기전에]

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

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


<기본 정의 >


1. State Pattern 정의

 

  -객체의 내부 상태가 바뀜에 따라서 객체의 행동을 바꿀 수 있다. 마치 객체의 클래스가 바뀌는 것과 같은 결과를 얻을 수 있다

    (Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.)



2. UML Diagram





3. 사용용도 및 장점


  -객체의 상태에 따라서, 동일한 동작이라도 다른 처리를 해야할때 사용한다.


  -하나의 객체에 여러가지의 상태가 존재할때 보통 IF문이나 SWITCH 문으로 분기를 해서 결과를 처리한다. 그러나 신규 상태가

   존재할때마다 기존의 IF문이나 SWITCH이 다시 수정되어야 한다. 객체의 상태를 클래스화해서 그것을 참조하게 하는식으로 

   소스의 변화를 최소화 할 수 있다.


  -예를 들어서 통장의 잔고의 상태에 따라서 이자가 높아지거나 ATM기를 이용할때 수수료가 할인되는등의 상태를 처리할때

   고객등급을 구분하게 된다면, 일반상태/실버상태/프리미엄으로 구별해서 처리가 가능하다. 여기에서 향후에 플래티넘이 추가

   된다 할지라도 플래티넘에 해당하는 상태 클래스를 State에서 상속받아서 처리한후에 Context에 할당만 해주면 된다.



4. 소스


using System;
 
namespace DoFactory.GangOfFour.State.Structural
{
  /// <summary>
  /// MainApp startup class for Structural
  /// State Design Pattern.
  /// </summary>
  class MainApp
  {
    /// <summary>
    /// Entry point into console application.
    /// </summary>
    static void Main()
    {
      // Setup context in a state
      Context c = new Context(new ConcreteStateA());
 
      // Issue requests, which toggles state
      c.Request();
      c.Request();
      c.Request();
      c.Request();
 
      // Wait for user
      Console.ReadKey();
    }
  }
 
  /// <summary>
  /// The 'State' abstract class
  /// </summary>
  abstract class State
  {
    public abstract void Handle(Context context);
  }
 
  /// <summary>
  /// A 'ConcreteState' class
  /// </summary>
  class ConcreteStateA : State
  {
    public override void Handle(Context context)
    {
      context.State = new ConcreteStateB();
    }
  }
 
  /// <summary>
  /// A 'ConcreteState' class
  /// </summary>
  class ConcreteStateB : State
  {
    public override void Handle(Context context)
    {
      context.State = new ConcreteStateA();
    }
  }
 
  /// <summary>
  /// The 'Context' class
  /// </summary>
  class Context
  {
    private State _state;
 
    // Constructor
    public Context(State state)
    {
      this.State = state;
    }
 
    // Gets or sets the state
    public State State
    {
      get { return _state; }
      set
      {
        _state = value;
        Console.WriteLine("State: " +
          _state.GetType().Name);
      }
    }
 
    public void Request()
    {
      _state.Handle(this);
    }
  }
}

-위에서는 내부의 상태를 2가지 중에서 토글하는 형태로 변경이 된다.

-상태는 여러가지가 가능하며 여기에서는 간단히 상태가 바뀐다는 것을 나타내려고 2가지로 구분했다.



5. 실행결과


State: ConcreteStateA

State: ConcreteStateB

State: ConcreteStateA

State: ConcreteStateB

State: ConcreteStateA




< 실제 적용 >


1. UML Diagram





2. 사용용도 및 장점


 -스타크래프트에는 유닛에게 걸수 있는 다양한 상태이상이 존재합니다. 메딕이 블라인드라는 스킬을 적유닛에게 걸면 대상 유닛은 시야가

  대폭줄어들게 됩니다. 또한 고스트라는 테란유닛은 상대 기계유닛을 이동불가 및 공격불가능하게 만들어 버립니다.

  이밖에도 여러가지의 상태가 존재합니다. 몇가지의 상태만 존재할때는 IF문이나 SWITCH문으로 분기해서 처리하면 됩니다. 하지만 다른 상태가

  추가적으로 업데이트가 된다면 소스가 수정이 많은 부분에서 되어야 할 것입니다. 그래서 이 상태자체를 객체화해서 추가만 해주면 상태추가가

  완료됩니다.

  

 -패턴을 사용하는 것 자체가 소스수정은 최소로 하고 확장성에는 개방이기 때문입니다.



3. 소스


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

namespace State
{
    class Program
    {
        static void Main(string[] args)
        {
            SiegeTank tank1 = new SiegeTank(new NormalState());

            tank1.Attack();
            tank1.Move();

            Console.WriteLine("\n");

            tank1.Attacked("Medic Blind");

            tank1.Attack();
            tank1.Move();

            Console.WriteLine("\n");

            tank1.Attacked("Ghost LockDown");
            tank1.Attack();
            tank1.Move();

            Console.ReadKey();

        }

        abstract class UnitState
        {
            public abstract void Attack();
            public abstract void Move();
        }

        class NormalState : UnitState
        {

            public override void Attack()
            {
                Console.WriteLine("공격 가능");
            }

            public override void Move()
            {
                Console.WriteLine("이동 가능 시야 10");
            }
        }

        class Blind : UnitState
        {

            public override void Attack()
            {
                Console.WriteLine("공격 가능");
            }

            public override void Move()
            {
                Console.WriteLine("이동 가능 시야 1");
            }
        }

        class LockDown : UnitState
        {

            public override void Attack()
            {
                Console.WriteLine("공격 불가!");
            }

            public override void Move()
            {
                Console.WriteLine("이동 불가!");
            }
        }

        class SiegeTank
        {
            private UnitState unitState;

            public SiegeTank(UnitState _unitState)
            {
                this.unitState = _unitState;
            }

            public void Attacked(string _StateAttack)
            {
                switch (_StateAttack)
                {
                    case "Medic Blind":
                        Console.WriteLine("메딕에게 블라인드 상태이상 공격을 받음");
                        this.unitState = new Blind();
                        break;
                    case "Ghost LockDown":
                        Console.WriteLine("고스트에게 락다운 상태이상 공격을 받음");
                        this.unitState = new LockDown();
                        break;
                    default:
                        break;
                }
            }

            public void Attack()
            {
                unitState.Attack();
            }

            public void Move()
            {
                unitState.Move();
            }

        }
    }
}

 - tank1.Attacked("Medic Blind") : 이부분은 할당을 하는부분이 아니라 외부에서 어떤 입력으로 받았다고 생각하시면 됩니다.


4. 실행결과




5. 피드백


 -상태패턴은 전략패턴과 유사합니다. 가장큰 차이점은 전략패턴은 클라이언트에서 어떤 전략 객체를 사용할지를 지정하지만

  상태패턴은 객체의 내부의 상태에 따라서 자동으로 상태가 변하게 됩니다.



[참조]

 -http://www.dofactory.com

 -Head First Design Patterns

And