在 C++ 的状态模式实现中避免 public `SetState()` 接口
Avoid public `SetState()` interface in state pattern implementation in C++
它本身是实现状态机的非常好的模式,因为它允许将状态转换逻辑封装在状态本身中,并且添加新状态实际上变得更容易,因为您只需要在相关状态中进行更改。
但是,在描述状态应该如何改变时,通常会避免。
如果您在 Context
中实现状态更改逻辑,那么整个模式的要点都被遗漏了,但是如果您在状态中实现状态更改逻辑,则意味着您需要在 [=12= 中设置一个新状态].
最常见的方法是将public方法添加到Context
SetState()
并将对Context
的引用传递给状态对象,这样就可以设置一个新状态,但本质上它将允许用户在状态机之外更改状态。
为了避免这种情况,我采用了以下解决方案:
class IContext {
public:
virtual void SetState(unique_ptr<IState> newState) = 0;
}
class Context : public IContext {
private:
virtual void SetState(unique_ptr<IState> newState) override { ... };
}
但总的来说,在派生中更改方法范围 class 看起来不太好。
是否有另一种隐藏此界面的方法(朋友 class 不是一个选项,因为它需要为每个添加的状态更改 Context
class)?
您可以考虑让处理程序handle()
返回下一个状态...
class IState {
public:
virtual unique_ptr<IState> handle(Context&) = 0;
};
class StateA : public IState {
private:
// presented inline for simplicity, but should be in .cpp
// because of circular dependency.
//
virtual unique_ptr<IState> handle(Context& ctx) override
{
//...
if (/*...*/)
return make_unique(StateB{});
//... including other state switch..
return { nullptr }; // returning null indicates no state change,
// returning unique_ptr<>(this) is not really an option.
}
};
状态模式的目标是 hide/encapsulate 与 caller.However 不同的实现,调用者只需要知道它需要什么类型的实现。
不确定这有多大帮助,但我刚刚在 C# 中实现了一个示例状态机,它使用观察者模式和一点反射来获得状态模式的非常干净和封装的实现。
Context.cs:
using System;
using System.Collections.Generic;
using System.Linq;
public class Context
{
State State { get; set; }
List<State> States { get; }
public Context()
{
States = new()
{
new HappyState(),
new SadState(),
};
SetState<HappyState>();
}
void DoSomething() => State?.DoSomething();
string ReturnSomething() => State?.ReturnSomething();
void SetState<StateType>() where StateType : State => SetState(typeof(StateType));
void SetState(Type stateType)
{
if (!stateType.IsSubclassOf(typeof(State))) return;
var nextState = States.Where(e => e.GetType() == stateType).First();
if (nextState is null) return;
if (State is not null)
{
State?.ExitState();
State.ChangeRequested -= OnChangeRequested;
}
State = nextState;
State.ChangeRequested += OnChangeRequested;
State.EnterState();
}
void OnChangeRequested(Type stateType) => SetState(stateType);
}
State.cs:
using System;
public abstract class State
{
public event Action<Type> ChangeRequested;
protected void SetState<StateType>() where StateType : State
{
ChangeRequested?.Invoke(typeof(StateType));
}
public virtual void EnterState() { }
public virtual void ExitState() { }
public virtual void DoSomething() { }
public virtual string ReturnSomething() => "";
}
然后您可以在上下文或任何状态中使用此语法
SetState<HappyState>();
它本身是实现状态机的非常好的模式,因为它允许将状态转换逻辑封装在状态本身中,并且添加新状态实际上变得更容易,因为您只需要在相关状态中进行更改。
但是,在描述状态应该如何改变时,通常会避免。
如果您在 Context
中实现状态更改逻辑,那么整个模式的要点都被遗漏了,但是如果您在状态中实现状态更改逻辑,则意味着您需要在 [=12= 中设置一个新状态].
最常见的方法是将public方法添加到Context
SetState()
并将对Context
的引用传递给状态对象,这样就可以设置一个新状态,但本质上它将允许用户在状态机之外更改状态。
为了避免这种情况,我采用了以下解决方案:
class IContext {
public:
virtual void SetState(unique_ptr<IState> newState) = 0;
}
class Context : public IContext {
private:
virtual void SetState(unique_ptr<IState> newState) override { ... };
}
但总的来说,在派生中更改方法范围 class 看起来不太好。
是否有另一种隐藏此界面的方法(朋友 class 不是一个选项,因为它需要为每个添加的状态更改 Context
class)?
您可以考虑让处理程序handle()
返回下一个状态...
class IState {
public:
virtual unique_ptr<IState> handle(Context&) = 0;
};
class StateA : public IState {
private:
// presented inline for simplicity, but should be in .cpp
// because of circular dependency.
//
virtual unique_ptr<IState> handle(Context& ctx) override
{
//...
if (/*...*/)
return make_unique(StateB{});
//... including other state switch..
return { nullptr }; // returning null indicates no state change,
// returning unique_ptr<>(this) is not really an option.
}
};
状态模式的目标是 hide/encapsulate 与 caller.However 不同的实现,调用者只需要知道它需要什么类型的实现。
不确定这有多大帮助,但我刚刚在 C# 中实现了一个示例状态机,它使用观察者模式和一点反射来获得状态模式的非常干净和封装的实现。
Context.cs:
using System;
using System.Collections.Generic;
using System.Linq;
public class Context
{
State State { get; set; }
List<State> States { get; }
public Context()
{
States = new()
{
new HappyState(),
new SadState(),
};
SetState<HappyState>();
}
void DoSomething() => State?.DoSomething();
string ReturnSomething() => State?.ReturnSomething();
void SetState<StateType>() where StateType : State => SetState(typeof(StateType));
void SetState(Type stateType)
{
if (!stateType.IsSubclassOf(typeof(State))) return;
var nextState = States.Where(e => e.GetType() == stateType).First();
if (nextState is null) return;
if (State is not null)
{
State?.ExitState();
State.ChangeRequested -= OnChangeRequested;
}
State = nextState;
State.ChangeRequested += OnChangeRequested;
State.EnterState();
}
void OnChangeRequested(Type stateType) => SetState(stateType);
}
State.cs:
using System;
public abstract class State
{
public event Action<Type> ChangeRequested;
protected void SetState<StateType>() where StateType : State
{
ChangeRequested?.Invoke(typeof(StateType));
}
public virtual void EnterState() { }
public virtual void ExitState() { }
public virtual void DoSomething() { }
public virtual string ReturnSomething() => "";
}
然后您可以在上下文或任何状态中使用此语法
SetState<HappyState>();