我想知道是否有针对以下问题的例外解决方案:
下面我有SomeClass,它有一个可以执行多个动作(命令)的成员函数。各个动作由另一个成员函数执行,该函数总是使对象处于良好且可预测的状态。
问题是如何处理执行某些操作而没有错误然后一个操作导致异常的情况。现在应该做些什么。
我看到这些解决方案,但感觉不好:
a)将异常传递给'ExecuteMultipleCommands'的调用者。这使对象处于良好但不可预测的状态(不知道执行了什么操作)。
b)在一次失败后继续执行命令。如果命令不是独立的,那么这是一个问题,也很难知道返回给调用者的内容。
c)在第一个异常时尝试恢复已经完成的操作,以便对象返回到“ExecuteMultipleCommands”调用之前的状态。现在,在“回滚”期间可能会发生另一个异常。
下面的代码不是真正的代码,但应该显示我的问题:
class SomeClass
{
public:
struct Command
{
/*...*/
};
void ExecuteOneCommand( const Command &oneCommand )
{
/* either completely executes a command or throws exception and leave object in unchanged state */
}
void ExecuteMultipleCommands( const vector< Command > &commands )
{
vector< Command >::const_iterator it = commands.begin();
for ( ; it != commands.end(); ++it )
{
try
{
ExecuteOneCommand( *it );
}
catch( /* some exception type */ )
{
/* what to do ? */
}
}
}
};
是否有关于此问题或其他出版物的设计模式?我搜索过但几乎空了。
注意:代码是真实问题的简化版本。在实际代码中,SomeClass实例持有的多个对象将在命令期间更改。这将使得使用SomeClass实例的副本变得更加困难,并且如果没有例外,则将其替换为原始实例。
此外,可以根据对象的当前状态来确定命令。就像您必须先将键/值对添加到地图中,然后才能更改值。这并不意味着'更改命令'必须始终与'添加命令'组合,因为密钥也可能已经存在。
答案 0 :(得分:1)
我会冒险回答,但我觉得这个问题有点难以理解。
我会使用你的对象的副本。
而不是直接在您的对象上执行: - 制作副本 - 执行该对象上的命令 如果没有触发异常 - 通过副本返回副本/替换原件 其他 - 保留原件 (在你的情况下,捕获保留原始)
我还会承认封装你的命令: 我的意思是有一个必须一起执行的命令列表。
通过这种方式,您可以将代码转换为类似的代码 class SomeClass
{
public:
struct Command
{
/*...*/
};
void ExecuteOneCommand( const Command &oneCommand )
{
/* either completely executes a command or throws exception and leave object in unchanged state */
}
SomeClass ExecuteCommands( const vector< Command > &commands )
{
SomeObject save = getCopy();
try
{
ExecuteMultipleCommands(commands);
}catch( /* some exception type */ )
{
return save
}
return this;
}
void ExecuteMultipleCommands( const vector< Command > &commands )
{
vector< Command >::const_iterator it = commands.begin();
for ( ; it != commands.end(); ++it )
{
ExecuteOneCommand( *it );
}
}
};
编辑 我刚刚意识到,如果从类中提取保存部分,这会更好用:
SomeObject save = someObjectInstance.copy();
if(!someObjectInstance.executeCommands){
//free someObjectInstance
someObjectInstance = save;
}
答案 1 :(得分:0)
我不知道有什么最好的方法可以做到这一点。这取决于你的命令在做什么。如果某些命令依赖于其他命令,则每个命令中都应该有一个依赖项列表,每个命令中都有一个表示完成的标志。一旦命令被调用,它应该查看依赖关系以确保它们在运行之前都是完整的。如果您需要“回滚”命令,您可以只复制命令以运行它,如果它运行没有异常,则将临时文件复制到原始文件并将complete设置为true。如果您需要在依赖项失败时回滚,则需要保留附加到已完成原始文档的原始副本,将其复制回来并在从属项失败时将其标记为不完整。如果不了解您的应用程序,很难知道您的要求是什么。
答案 2 :(得分:0)
我认为只有ExecuteMultipleCommands
的调用者才能充分处理异常。为了便于异常处理,您可以在ExecuteMultipleCommands
中更改catch块并在异常中添加额外信息:
void ExecuteMultipleCommands( const vector< Command > &commands )
{
size_t command_index = 0;
try
{
for ( command_index = 0; command_index < commands.size() ; command_index++)
{
ExecuteOneCommand(commands[command_index]);
}
}
catch (OneCommandException &e)
{
MultipleCommandException new_exception = MultipleCommandException(e);
// assuming MultipleCommandException has a public constructor that accepts OneCommandException&
new_exception.command_index = command_index;
throw new_exception;
}
}
答案 3 :(得分:0)
这通常如何运作是通过复制。例如,.NET中的LINQ to SQL将处理内存中的副本,然后立即处理所有更改,并在发生任何故障时回滚所有更改。
通常,您可以保证在回滚期间不会因为处理副本而发生异常 - 也就是说,如果您没有成功地明确地达到所有更改的结尾,那么更改将永远不会提交,因此回滚的行为是只是破坏副本,这是一个固有的安全程序。