我的程序有一个操作,它由一系列单个操作组成,这些操作都改变了全局状态。每个基本操作都可能失败并使全局状态处于未定义状态。
是否有一个通用模式可以帮助我使组合操作“原子化”,即如果其中一个子操作失败,全局状态保持不变?
我使用C ++,所以如果答案包含代码,如果您有选择,请更喜欢该语言。但我不介意其他语言的例子。
这类似于数据库的“原子性”,在执行提交时,您可以在其中添加全部或全部内容。这是如何实现的?
在我的情况下,我的全局状态是文件系统的状态。我需要一次添加或删除多个文件,并希望确保它们都已添加或未添加任何内容。
我能想到的最好的是一个获取操作列表及其逆操作的类。如果任何操作失败,它将执行已执行操作的反向操作。但如果其中一个反向操作失败该怎么办?
该类的界面如何?我是否会有一个类似于可逆操作的额外课程?你知道一个我可以阅读更多关于这个问题的地方吗?
答案 0 :(得分:2)
基本解决方案是commit-or-rollback。
通用C ++实现是构建支持3种方法的对象列表:
run()
将执行可能失败的所有步骤,而不会产生可见的副作用。如果失败commit()
可能不会抛出,只能在run()
可见rollback()
可能不会抛出,只能撤消run()
然后遍历该列表,在所有内容上调用run()
。如果有任何抛出,请抓住它并rollback()
成功运行的所有东西。如果没有,则commit()
全部抛出。
您的情况的近似工作流程是 - 对于每个文件创建对象:
调用run()
在正确的目录中创建(并填充?)文件,但是使用临时名称 - 理想情况下隐藏
如果失败,请在每个成功rollback()
文件创建对象上调用run()
。这必须删除临时文件,并且可能不会失败。
然后,放弃,从头开始重试,提示用户或其他什么。
commit()
。这必须将临时文件重命名为其最终名称,并且可能不会失败这要求commit
步骤不会失败。在这种情况下,假设重命名文件不会失败 - 你必须确保这是真的(即,没有名称冲突)之前你到达commit
阶段。
请注意,此 具有中间非原子更改 - 通常避免这种情况的唯一方法是拥有全局状态的两个副本,以及提交修改后的原子交换的原子交换。即使您正在编写驱动程序,也不能为文件系统执行此操作。
答案 1 :(得分:1)
当我第一次阅读你的问题时,我想到了这样的事情:
bool ApplyStateTransitions(Transitions& transitions) {
auto lock = BlockUntilLocked(); // begin critical section
auto clone = mCurrentState.Clone(); // copy all the state
auto success = clone.ExecuteTransitions(transitions);
if (!success)
return false;
mCurrentState = clone;
return true;
}
您可以使用不可变状态模式执行类似的方法。
通常在状态机中复制当前状态并不昂贵;他们只是没有那么多州。或者它更像是数据库表?
数据库引擎使用具有回滚或提交方法的事务。您可以在此处阅读该方法:https://en.wikipedia.org/wiki/ACID
答案 2 :(得分:1)
使用Unit of Work EAA设计模式。