2022. 3. 17. 00:00ㆍCSharp/Design Pattern
내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 하는 동작 디자인 패턴
객체가 특정 상태에 따라 행위를 달리하는 상황에서,
자신이 직접 상태를 체크하여 상태에 따라 행위를 호출하지 않고,
상태를 객체화 하여 상태가 행동을 할 수 있도록 위임하는 패턴
문제점
특정 class 의 state 가 여러가지 있고 이 각각의 state 를 처리 하려면
조건문을 부득이 하게 사용하게 된다.
이러한 조건문은 state 가 늘어 날 수록 복잡해 지게 되고
각 조건들은 또한 세부 조건을 갖게 되어 유지보수가 힘들어 지는 class 가 생성된다.
해결책
각 state 들의 처리를 하나의 class 에서 하지 않고
조건에 따라 각각 다른 state class 로 캡슐화 하여 context 라는 원래 개체에서 논리를 숨긴다.
또한 context 는 state class 중 하나에 대한 참조를 저장하고 모든 상태관련 작업을 state 에 위임한다.

class diagram

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.
방어 : 방패를 들어올림
관련영상
'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 |