我有一个需要支持播放的状态机设计。我们有执行操作的状态,有时需要生成随机数。如果程序在FSM执行过程中关闭,程序需要使用与以前相同的随机数回放整个FSM。
对于一个基本的例子,假设我有三种状态:A,B和C.FSM将调用状态的Execute()函数。在函数结束时,状态将发布一个事件,FSM将确定下一个要进入的状态。在状态A中,它将调用rand()。如果数字是偶数,它将发布一个事件进入状态B,否则状态C应该是下一个状态。
void StateA::Execute(IEventQueue& rQueue)
{
int num = rand();
if( num % 2 == 0 )
{
rQueue.PostEvent("GoToStateB");
}
else
{
rQueue.PostEvent("GoToStateC");
}
}
如果随机数是69,那么它应该进入状态C.而在状态C中,程序可能会退出。当程序再次启动时,它应该回放状态机。显然,为了正常工作,它不能生成一个全新的随机数,它需要再次使用69才能准确播放。
我有一个文件流界面可用于将数据保存到文件中,但代码有点难看:
void StateA::Execute(IEventQueue& rQueue, IFileStream& rStream)
{
int num = 0;
// fails if there's no more data to read
bool bSuccess = rStream.ReadInt(num);
if (!bSucess)
{
num = rand();
rStream.WriteInt(num);
}
// same code as before
}
我对此解决方案的唯一问题是我不必先检查数据流,然后有条件地写入同一个流。
我想这样隐藏它:
void StateA::Execute(IEventQueue& rQueue, IStream& rStream)
{
int num = 0;
num = rand();
rStream & num;
// same code as before
}
内部IStream,运营商& (可能不是最好的重载使用)实际上会尝试从流中读取一个int。如果该流为空,则将其写入。像以前一样,行为将是:首先读取直到流结束,然后开始追加。
所以我想我的问题是:这种类型的播放有一个常见的习惯用法,我可能会忽略它吗?有人有其他建议吗?我觉得我开始过度复杂化设计了。
谢谢!
答案 0 :(得分:1)
为什么状态直接与文件流交互?单一职责说我们应该有一个班级,根据一些逻辑提供正确的数字。
struct INumberSource {
virtual int GenNextNumber() = 0;
}
// My job is to provide numbers from an RNG
struct RNGNumberSource : public INumberSource {
virtual int GenNextNumber() {
return rand();
}
}
// My job is to write any numbers sourced through me to a file
// I delegate to another source to get an actual number
class FileStreamTrackingNumberSource : INumberSource {
public:
FileStreamTrackingNumberSource(INumberSource& source, IFileStream& stream)
: altSource(source), fileStream(stream) { }
virtual int GenNextNumber() {
int num = altSource.GenNextNumber();
fileStream.WriteInt(num);
return num;
}
private:
INumberSource altSource;
IFileStream& fileStream;
}
// My job is to source numbers from a file stream delegating to an
// alternate source when I run out
class FileStreamNumberSource : public INumberSource {
public:
FileStreamNumberSource(INumberSource& source, IFileStream& stream)
: altSource(source), fileStream(stream), failedRead(false) { }
virtual int GenNextNumber() {
int num = 0;
if(failedRead || !(failedRead = fileStream.ReadInt(num))) {
num = altSource.GenNextNumber();
}
return num;
}
private:
INumberSource& altSource;
IFileStream& fileStream;
bool failedRead;
}
因此,在您的情况下,您会向IFileStream
提供RNGNumberSource
和FileStreamTrackingNumberSource
,并向IFileStream
提供相同的FileStreamNumberSource
。 FileStreamNumberSource
就是您对州的INumberSource
参数所提供的内容。
假设您只需要该号码来选择下一个州,那么您的州代码可能如下所示:
void StateA::Execute(IEventQueue& rQueue, INumberSource& numberSource)
{
if( numberSource.GenNextNumber() % 2 == 0 )
{
rQueue.PostEvent("GoToStateB");
}
else
{
rQueue.PostEvent("GoToStateC");
}
}
答案 1 :(得分:0)
我怀疑你应该有两个文件:一个记录你正在播放的事件,另一个你读“重播”事件。如果重播文件比“录制”文件长,那么这就是你用来重播的文件。
我也不会像你建议的那样使用运算符重载。也许只使用三元运算符。
答案 2 :(得分:0)
我不确定我理解“回放”背后的基本原理,但是你不能简单地将整个“随机数或读取文件”逻辑包装在一个类或函数后面吗?
<强>更新
关于“回放”的主题和你的设计一般来说,我不确定FSM产生自己的刺激(即随后触发状态转换的随机数)是正常的。通常,刺激是在外部提供的。如果你考虑到这一点,那么你就不再有这个混乱的问题!