상태 패턴

정선호·2023년 5월 2일
0

Design Patterns

목록 보기
2/24

관련 영상

상태 패턴(State Pattern)

설명 및 스도코드

  • 코드(특히 객체지향 프로그래밍)에서 상태의 개념(Concept)를 이용하는 방법
  • 각각의 상태 코드는 스스로 온전함(self-contained)
    • 각각의 상태에 따른 행위를 독립적으로 변경할 수 있게 해 준다
    • 어떠한 상태로의 변경이 다른 상태들에게 영향을 끼치지 않는다

상태(State)

  • 객체(Object)나 시스템(System)의 현 상황(Condition)
  • 객체나 시스템의 행위(Behavior)를 결정한다

유한 상태 머신(Finate State Machine)

  • 유한 상태 머신이란?
    • 유한(Finate)이란 유한한 수의 상태가 있다는 의미이다
    • 우리가 프로그래밍하는 상태의 수에만 제한된다
    • 동시에 단 하나의 상태만 존재할 수 있다
    • 상태는 오브젝트의 행위를 지정한다
    • 상태 내에는 다른 상태로의 전환(transition)도 포함하고 있다
  • 유한 상태 머신을 구현하는 방법
    1. 머신에 대해 각각의 상태를 정의한다
    2. 상태 사이의 전환을 정의한다
    3. 초기 상태(Initial state)를 설정한다

상태 패턴 구조

  • 콘텍스트(Context)
    • 구상 상태 객체 중 하나에 대한 참조를 저장하고 모든 상태별 작업을 그곳에 위임
    • 상태 인터페이스를 통해 상태 객체와 통신하며 새로운 상태 겍체를 전달하기 위한 setter를 노출
  • 상태 인터페이스(State Interface)
    • 상태별 메서드들을 선언
    • 메서드들은 모든 구상 상태에서 유효해야 한다
  • 구상 상태(Concrete States)
    • 상태별 메서드들에 대한 자체적인 구현 제공
    • 여러 상태에서 유사한 코드의 중복을 피하기 위하여 어떤 공통 행동을 캡슐화하는 중간 추상 클래스들을 제공할 수 있다
    • 콘텍스트 객체에 대한 역참조를 지정해 콘텍스트 객체에서 필요한 모든 정보 혹은 상태천이가 가능하다

콘텍스트와 구상 상태들 모두 콘텍스트의 다음 상태를 설정할 수 있으며, 콘텍스트에 연결된 상태 객체를 교체하여 실제 상태 천이를 수행할 수 있다.

상태 패턴의 적용

  • 현재 상태에 따라 다르게 행동하는 객체가 있을 때, 상태들의 수가 많을 때, 그리고 상태별 코드가 자주 변경될 때
  • 클래스 필드들의 현재 값들에 따라 클래스가 행동하는 방식을 변경하는 거대한 조건문들로 오염된 클래스가 있을 때
  • 유사한 상태들에 중복 코드와 조건문-기반 상태 머신의 천이가 많을 때

다른 패턴과의 관계

  • 브리지, 상태, 전략 패턴은 매우 유사한 구조로 되어 있으며, 어댑터 패턴도 이들과 어느 정도 유사한 구조로 되어 있다.
    • 위 모든 패턴은 다른 객체에 작업을 위임하는 합성을 기반으로 한다. 하지만 이 패턴들은 모두 다른 문제들을 해결한다.
    • 패턴은 특정 방식으로 코드의 구조를 짜는 레시피에 불과하지 않는다. 왜냐하면 패턴은 해결하는 문제를 다른 개발자들에게 전달할 수도 있기 때문이다.
  • 상태는 전략의 확장으로 간주할 수 있다. 두 패턴 모두 합성을 기반으로 한다. 그들은 어떤 작업을 도우미 객체들에 전달하여 콘텍스트의 행동을 바꾼다.
    • 전략 패턴은 이러한 객체들을 완전히 독립적으로 만들어 서로를 인식하지 못하도록 만든다. 그러나 상태는 구상 상태들 사이의 의존 관계들을 제한하지 않으므로 그들이 콘텍스트의 상태를 마음대로 변경할 수 있도록 한다.

Unity C#으로 구현된 상태 머신

  • 상태
namespace StateMachine
{
    public abstract class State<T>
    {
        protected StateMachine<T> _stateMachine;
        protected T _context;

		// 상태 설정 함수
        internal void SetContext(StateMachine<T> stateMachine, T context)
        {
            this._stateMachine = stateMachine;
            this._context = context;
            
            OnInitialized();
        }
        
        // 상태를 초기화시킬 때 호출되는 함수 (Awake와 유사)
        public virtual void OnInitialized()
        {}
        
        // 상태에 진입할 때 호출되는 함수 (Start와 유사)
        public virtual void OnEnter()
        {}

		// 상태의 Update 함수, 이 곳에서 다른 상태로의 전환 조건을 걸 수 있음
        public abstract void Update();
        
        public virtual void FixedUpdate()
        {}

		// 상태를 종료할 때 호출되는 함수
        public virtual void OnExit()
        {}
    }
}
  • 상태 머신
namespace StateMachine
{
    public class StateMachine<T>
    {
        private T _context;

        private State<T> _currentState;
        public State<T> CurrentState => _currentState;

        private State<T> _previousState;
        public State<T> PreviousState => _previousState;

        private Dictionary<Type, State<T>> _states = new ();


        
        #region Constructor

        public StateMachine(T context, State<T> initialState)
        {
            this._context = context;

            AddState(initialState);
            _currentState = initialState;
            _currentState.OnEnter();
        }

        #endregion
        
        
        
        #region State Modifying Methods
        
        public void AddState(State<T> state)
        {
            state.SetContext(this, _context);
            _states[state.GetType()] = state;
        }

        public void RemoveState(State<T> state)
        {
            foreach (var st in _states)
            {
                if (st.Key == state.GetType())
                {
                    _states.Remove(state.GetType());
                    break;
                }
            }
        }

        public void RemoveAllState()
        {
            _states.Clear();
        }
        
        public void ChangeState<T2>() where T2 : State<T>
        {
            if (typeof(T2) == _currentState.GetType())
                return;

            if (_currentState != null)
                _currentState.OnExit();
            _previousState = _currentState;
            _currentState = _states[typeof(T2)];
            _currentState.OnEnter();
        }

        #endregion



        #region Update State Methods

        public void Update()
        {
            _currentState.Update();
        }

        public void FixedUpdate()
        {
            _currentState.FixedUpdate();
        }

        #endregion



        #region Check State Methods

        public bool IsCurrentState<T2>() where T2 : State<T>
        {
            if (typeof(T2) == _currentState.GetType())
            {
                return true;
            }
            return false;
        }

        #endregion
    }
}
profile
학습한 내용을 빠르게 다시 찾기 위한 저장소

0개의 댓글