State<T>
강력한 타입의 액터 참조를 사용하여 상태 머신 상태를 구현하기 위한 제네릭 기본 클래스입니다.
네임스페이스: ControlBee.Models
구현: IState, IDisposable
개요
State<T>는 액터 상태를 구현하기 위한 권장 기본 클래스입니다. 다음을 제공합니다:
- 강력한 타입의 액터 액세스 - 제네릭 타입 매개변수를 통해
- IState 인터페이스 구현 - 메시지 처리를 위해
- TimeManager에 대한 액세스 - 타이머 작업을 위해
- Disposable 패턴 - 정리를 위해
IState를 직접 구현하는 것과 달리, State<T>는 액터의 속성 및 메서드에 액세스할 때 컴파일 타임 타입 안전성을 제공합니다.
제네릭 매개변수
public abstract class State<T>(T actor) : IState
where T : Actor
타입 매개변수:
T— 이 상태가 속한 특정 액터 타입입니다.Actor를 상속해야 합니다.
이점:
- 액터에 액세스할 때 캐스팅이 필요 없음
- 액터 멤버에 대한 IntelliSense 지원
- 컴파일 타임 타입 검사
- 리팩토링 안전성
생성자
protected State(T actor)
매개변수:
actor— 이 상태를 소유하는 액터 인스턴스
사용 예시:
public class IdleState : State<StageActor>
{
public IdleState(StageActor actor) : base(actor)
{
}
}
Protected 멤버
Actor
protected T Actor { get; }
이 상태를 소유하는 강력한 타입의 액터 인스턴스입니다.
사용 예시:
public class ProcessingState : State<StageActor>
{
public ProcessingState(StageActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
if (message.Name == "Start")
{
// StageActor 속성에 직접 액세스 - 캐스팅이 필요 없음
Actor.X.TrapezoidalMove(Actor.TargetPos.Value.X, 100, 200, 200);
Actor.Vacuum.TurnOn();
Actor.Busy.Value = true;
return true;
}
return false;
}
}
TimeManager
protected ITimeManager TimeManager { get; }
시간 관리자에 액세스하기 위한 편의 속성입니다. Actor.TimeManager와 동일합니다.
사용 예시:
public class TimedState : State<MyActor>
{
public TimedState(MyActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
if (message.Name == "StartOperation")
{
var stopwatch = TimeManager.StartNew();
PerformOperation();
var elapsed = stopwatch.ElapsedMilliseconds;
Logger.Info($"Operation took {elapsed}ms");
return true;
}
return false;
}
}
추상 메서드
ProcessMessage
public abstract bool ProcessMessage(Message message);
들어오는 메시지를 처리하기 위해 파생 상태에 의해 구현되어야 합니다.
반환값: 메시지가 처리되면 true, 그렇지 않으면 false입니다.
참고: 자세한 문서는 IState.ProcessMessage를 참조하세요.
가상 메서드
Dispose
public virtual void Dispose()
상태가 폐기될 때 호출됩니다. 정리를 수행하려면 재정의하세요.
기본 구현: 비어 있음(아무것도 하지 않음).
사용 예시:
public class ResourceState : State<MyActor>
{
private Timer? timer;
public ResourceState(MyActor actor) : base(actor)
{
timer = new Timer(OnTimer, null, 1000, 1000);
}
public override bool ProcessMessage(Message message)
{
// ... 메시지 처리 ...
return false;
}
public override void Dispose()
{
timer?.Dispose();
timer = null;
}
}
사용 패턴
간단한 상태
public class IdleState : State<StageActor>
{
public IdleState(StageActor actor) : base(actor) { }
public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case "Start":
Actor.SetState(new ProcessingState(Actor));
return true;
case "Initialize":
Actor.SetState(new InitializingState(Actor));
return true;
default:
return false;
}
}
}
진입/종료 로직이 있는 상태
public class ProcessingState : State<StageActor>
{
public ProcessingState(StageActor actor) : base(actor)
{
// 진입 로직
Actor.Busy.Value = true;
Actor.StatusLight.TurnOn();
Actor.X.Enable = true;
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "Complete")
{
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
// 종료 로직
Actor.Busy.Value = false;
Actor.StatusLight.TurnOff();
Actor.X.Enable = false;
}
}
타이머가 있는 상태
public class WaitingState : State<StageActor>
{
private readonly Timer timer;
public WaitingState(StageActor actor) : base(actor)
{
// 주기적인 확인 시작
timer = new Timer(CheckCondition, null, 0, 100);
}
private void CheckCondition(object? state)
{
if (Actor.MaterialSensor.Value)
{
Actor.SetState(new ProcessingState(Actor));
}
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "Cancel")
{
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
timer.Dispose();
}
}
비동기 작업이 있는 상태
public class MovingState : State<StageActor>
{
private readonly CancellationTokenSource cts;
public MovingState(StageActor actor) : base(actor)
{
cts = new CancellationTokenSource();
StartMove();
}
private async void StartMove()
{
try
{
Actor.X.TrapezoidalMove(Actor.TargetPos.Value.X, 100, 200, 200);
// 타임아웃과 함께 완료 대기
await Task.Run(() => Actor.X.Wait(), cts.Token)
.WaitAsync(TimeSpan.FromSeconds(5), cts.Token);
// 이동 완료
Actor.Send(new Message(Actor, "MoveComplete"));
}
catch (OperationCanceledException)
{
// 취소됨
}
catch (TimeoutException)
{
Actor.SetState(new ErrorState(Actor, "Move timeout"));
}
}
public override bool ProcessMessage(Message message)
{
if (message.Name == "MoveComplete")
{
Actor.SetState(new IdleState(Actor));
return true;
}
if (message.Name == "Cancel")
{
cts.Cancel();
Actor.X.Stop();
Actor.SetState(new IdleState(Actor));
return true;
}
return false;
}
public override void Dispose()
{
cts.Cancel();
cts.Dispose();
}
}
모범 사례
1. 항상 제네릭 타입 사용
// ✅ 좋음 - 강력한 타입
public class MyState : State<MyActor>
{
public override bool ProcessMessage(Message message)
{
Actor.MyProperty = value; // 캐스팅이 필요 없음
return true;
}
}
// ❌ 나쁨 - 타입 안전성 상실
public class MyState : IState
{
private MyActor _actor;
public bool ProcessMessage(Message message)
{
((MyActor)_actor).MyProperty = value; // 캐스팅이 필요함
return true;
}
}
2. 생성자에서 초기화
// ✅ 좋음 - 생성자에 진입 로직
public ProcessingState(StageActor actor) : base(actor)
{
Actor.Busy.Value = true;
Actor.Motor.Start();
}
// ❌ 나쁨 - 초기화 누락
public ProcessingState(StageActor actor) : base(actor)
{
}
3. Dispose에서 정리
// ✅ 좋음 - Dispose에서 정리
public override void Dispose()
{
timer?.Dispose();
Actor.Motor.Stop();
Actor.Busy.Value = false;
}
// ❌ 나쁨 - 정리 없음
// 상태가 변경될 때 리소스 누수
4. 올바른 값 반환
// ✅ 좋음 - 처리되지 않은 메시지에 대해 false 반환
public override bool ProcessMessage(Message message)
{
if (message.Name == "KnownMessage")
{
HandleMessage();
return true;
}
return false; // 다른 핸들러가 시도하도록 허용
}
// ❌ 나쁨 - 항상 true 반환
public override bool ProcessMessage(Message message)
{
// ...
return true; // 메시지 전파 차단
}
참고
- IState 인터페이스 - 상태 인터페이스 정의
- 상태 관리 가이드 - 완벽한 상태 패턴 가이드
- 메시지 전달 가이드 - 메시지 처리
- 액터 시스템 가이드 - 액터 개발