如何处理多个操作和异常

发布于 2024-10-23 14:42:55 字数 1356 浏览 0 评论 0原文

我想知道以下问题是否有例外的解决方案:

下面我有 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 实例的副本变得更加困难,并且在没有发生异常的情况下将其替换为原始实例。

此外,命令可以取决于对象的当前状态。就像您必须首先将键/值对添加到映射一样,然后才能更改值。这并不意味着“更改命令”必须始终与“添加命令”组合使用,因为密钥也可能已经存在。

I would like to know if there is an excepted solution for the following problem:

Below I have SomeClass that has a member function that can perform multiple actions (commands). The individual actions are executed by another member function which always leaves the object in a good and predictable state.

The problem is how to deal with the situation where some actions are performed without error and then one action causes an exception. What should be done now.

I see these solutions but neither feels good:

a) Pass the exception on to the caller of 'ExecuteMultipleCommands'. This leaves the object in a good but unpredictable state (don't know what actions were executed).

b) Keep executing commands after one failed. This is a problem if the commands are not independend, also it is hard to know what to return to the caller.

c) At the first exception try to revert the actions already done so that the object goes back to the state before the call to 'ExecuteMultipleCommands'. Now another exception could happen during the 'rollback'.

The code below is not real code but should show my problem:

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 ? */
          }
      }
  }
};

Are there design patterns regarding this problem or maybe other publications? I've searched but came up almost empty.

Note: The code is simplified version of a real problem. In the real code multiple objects held by the SomeClass instance will change during commands. This will make it much harder to work with a copy of the SomeClass instance and replace that with the original if no exceptions happened.

Also the commands could be depended based on the current state of the object. Like you must first add a key/value pair to a map before you can change the value. This doesn't mean that the 'change command' must always be combined with an 'add command' because the key could also already be present.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

昵称有卵用 2024-10-30 14:42:55

我会冒险寻找答案,但我发现这个问题有点难以理解。

我会使用你的物品的副本。

而不是直接在您的对象上执行:
- 复印一份
- 在该对象上执行命令
如果没有触发异常
- 返回副本/用副本替换原件
别的
- 保留原件
(在你的情况下,在 catch 中保留原始内容)

我也同意封装你的命令:
我的意思是有一个必须一起执行的命令列表。

这样你就可以将你的代码转换为类似

  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;
}

I'll adventure an answer but I find the question a bit difficult to grasp.

I would work with copies of your objects.

instead of executing directly on your object:
- make a copy
- execute the commands on that object
if no exception triggered
- return the copy/ replace the original by the copy
else
- keep the original
(in your case in the catch keep the original)

I would also conceder encapsulating your commands:
by that I mean have a list of commands that have to be executed together.

this way you can transform your code to something like

  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 );
           }
      }
    };

Edit
I just realized this works better if you extract the save part from the class:

SomeObject save = someObjectInstance.copy();
if(!someObjectInstance.executeCommands){
//free someObjectInstance
someObjectInstance = save;
}
弥枳 2024-10-30 14:42:55

我不知道有什么最好的方法可以做到这一点。这取决于您的命令正在做什么。如果某些命令依赖于其他命令,则每个命令中应该有一个依赖项列表,并且每个命令中应该有一个指示完成的标志。一旦调用命令,它应该在运行之前检查依赖关系以确保它们全部完整。如果您需要“回滚”命令,您只需复制该命令即可运行它,如果它运行没有异常,则将临时命令复制到原始命令并将完成设置为 true。如果您需要在依赖项失败时回滚,则可能需要将原始副本附加到已完成的原始文件上,将其复制回来并在依赖项失败时将其标记为不完整。如果不了解更多有关您的应用程序的信息,就很难知道您的要求是什么。

I don't know that there's any best way to do this. It depends on what your commands are doing. If some commands are dependent on others, you should have a list of dependencies in each command, and a flag in each command that indicates completion. Once a command is called, it should look through dependencies to make sure they are all complete before being run. If you need the commands to 'roll back', you could just make a copy of the command to run it and if it runs without exceptions, copy the temp one over to the original one and set complete to true. If you need to roll back if a dependent fails, you'll need to maybe keep a copy of the original attached to the completed original, copy it back and mark it to incomplete when the dependent fails. It's hard to know what your requirements are without knowing more about your application.

绻影浮沉 2024-10-30 14:42:55

我认为只有 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;
    }
}

I think that only caller of ExecuteMultipleCommands can sufficiently handle exception. To facilitate exception handling you can change catch block in ExecuteMultipleCommands and add extra information to the exception :

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;
    }
}
鯉魚旗 2024-10-30 14:42:55

这通常是通过处理副本来实现的。例如,.NET 中的 LINQ to SQL 将处理内存中的副本,然后立即处理所有更改,并在出现任何故障时回滚所有更改。

通常,您通过处理副本来保证回滚期间不会发生异常 - 也就是说,如果您没有明确成功地到达所有更改的末尾,则更改永远不会提交,因此回滚的行为只是破坏了副本,这是一个本质上安全的程序。

How this normally works is by working on copies. For example, LINQ to SQL in .NET will work on in-memory copies, and then transact all the changes at once, and roll back all changes upon any failure.

Typically, you guarantee that no exceptions occur during rollback by working on a copy- that is, if you don't explicitly reach the end of all changes successfully, then the changes are never committed and therefore the act of rolling back is just destroying the copies, which is an inherently safe procedure.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文