State

2022. 3. 17. 00:00CSharp/Design Pattern

반응형

내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 하는 동작 디자인 패턴

객체가 특정 상태에 따라 행위를 달리하는 상황에서,

자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고,

상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴

 

 

문제점

특정 class 의 state 가 여러가지 있고 이 각각의 state 를 처리 하려면

조건문을 부득이 하게 사용하게 된다. 

이러한 조건문은 state 가 늘어 날 수록 복잡해 지게 되고

각 조건들은 또한 세부 조건을 갖게 되어 유지보수가 힘들어 지는 class 가 생성된다. 

 

해결책

각 state 들의 처리를 하나의 class 에서 하지 않고

조건에 따라 각각 다른 state class 로 캡슐화 하여 context 라는 원래 개체에서 논리를 숨긴다. 

또한 context 는 state class 중 하나에 대한 참조를 저장하고 모든 상태관련 작업을 state 에 위임한다.

 

https://refactoring.guru/design-patterns/state

 

class diagram

https://refactoring.guru/design-patterns/state

 

 

Context

: State 대한 참조를 저장하고 모든 상태별 작업을 위임.  

새로운 상태 객체를 전달하기 위한 setter를 노출

 

State (interface)

: 상태별 메소드를 선언

 

ConcreateState

: 상태별 메소드 구현,

컨텍스트 개체에 대한 역참조,

컨텍스트 개체에서 필요한 모든 정보를 가져오고 상태 전환을 시작

 

*** Context ConcreateState 모두 Context 의 다음 상태를 설정하고 컨텍스트에 연결된 상태 개체를 교체하여 실제 상태 전환을 수행할 수 있다. ***

 

적용

  • 현재 상태에 따라 다르게 동작하는 객체가 있고 상태의 수가 방대하고 상태별 코드가 자주 변경될 때
  • 클래스 필드의 현재 값에 따라 클래스가 작동하는 방식을 변경하는 대규모 조건으로 클래스가 오염된 경우
  • 유사한 상태 및 조건 기반 상태 머신의 전환에 걸쳐 중복 코드가 많은 경우 

 

전사의 attack 과 defence 를 예로 들어보자

전사의 상태에 따라 attack 과 defence 의 동작이 달라 지고 이를 구현 하려고 한다면 어떨까?

 

1. 보통 상태

   attack = 무기를 휘두름  (상태가 분노상태가 됨)

   defence = 방패를 들어올림 

 

2. 분노 상태

   attack = 무기를 두번 휘두름 (상태가 피곤 상태가 됨)

   defence = 방패를 휘두름 (상태가 피곤 상태가됨)

 

3. 피곤 상태

   attack = 밀쳐내기 

   defence = 휴식 (상태가 보통 상태가 됨)

 

위와 같은 상태를 state pattern 을 이용해 구현해 보자

 

Context 에 해당 하는 Warrior

public interface IUnitContext
{
    void Attack();
    void Defence();
    void TransitionTo(IUnitState state);
}
public abstract class UnitContext: IUnitContext
{
    protected IUnitState _state;
    public UnitContext(IUnitState state) => TransitionTo(state);

    public abstract void Attack();
    public abstract void Defence();
    public void TransitionTo(IUnitState state)
    {
        Console.WriteLine($"Context: Transition to {state.GetType().Name}.");
        this._state = state;
        this._state.SetContext(this);
    }
}

public class Warrior : UnitContext
{
    public Warrior(IUnitState state) : base(state)
    {
    }

    public override void Attack()
    {
        _state.Attack();
    }

    public override void Defence()
    {
        _state.Defence();
    }
}

State 

public interface IUnitState
{
    void SetContext(IUnitContext context);
    void Attack();
    void Defence();
}

public abstract class UnitState : IUnitState
{
    protected IUnitContext _context;
    public abstract void Attack();
    public abstract void Defence();
    public void SetContext(IUnitContext context) => _context = context;
}

public class NormalState : UnitState
{
    public override void Attack()
    {
        Console.WriteLine("공격 : 무기를 휘두름");
        _context.TransitionTo(new FuryState());
    }

    public override void Defence()
    {
        Console.WriteLine("방어 : 방패를 들어올림");
    }
}

public class FuryState : UnitState
{
    public override void Attack()
    {
        Console.WriteLine("공격 : 무기를 두번 휘두름");
        _context.TransitionTo(new TiredState());
    }

    public override void Defence()
    {
        Console.WriteLine("방어 : 방패를 휘두름");
        _context.TransitionTo(new TiredState());
    }
}

public class TiredState : UnitState
{
    public override void Attack()
    {
        Console.WriteLine("공격: 밀쳐내기");
    }

    public override void Defence()
    {
        Console.WriteLine("방어: 휴식");
        _context.TransitionTo(new NormalState());
    }
}

사용법

var context = new Warrior(new NormalState());
context.Attack();
context.Attack();
context.Attack();
context.Defence();
context.Defence();
// output
Context: Transition to NormalState.
공격 : 무기를 휘두름
Context: Transition to FuryState.
공격 : 무기를 두번 휘두름
Context: Transition to TiredState.
공격: 밀쳐내기
방어: 휴식
Context: Transition to NormalState.
방어 : 방패를 들어올림

 

관련영상

https://youtu.be/kG092oy2BfA

 

반응형

'CSharp > Design Pattern' 카테고리의 다른 글

Command  (0) 2022.03.21
Template Method  (0) 2022.03.18
Memento  (0) 2022.03.16
Iterator  (0) 2022.03.15
Chain of Responsibility  (0) 2022.03.14