开发者

How to handle multiple actions and exceptions

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 comm开发者_开发技巧ands 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.


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


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.


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜