1

In Elements of Reusable Object Oriented software by Erich Gamma, the intent for Command design pattern says:

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

What does the author mean by undoable operations? Hard to make out what he actually wants to refer here. I would appreciate a clarification.

  • An operation that can be undone. Like a file edit, or adding a picture to a slidedeck, or making this comment. What part of that is unclear? – Telastyn Dec 11 '18 at 1:47
  • 2
    Link – John Wu Dec 11 '18 at 2:05
  • To be fair it isn't unreasonable to interpret 'undoable' as not doable – J Lewis Dec 11 '18 at 10:28
  • 1
    @JLewis yes this was also coming to my mind...so i seeked clarification – Navjot Waraich Dec 11 '18 at 11:27
9

What does the author mean by undoable operations? Hard to make out what he actually wants to refer here. I would appreciate a clarification.

He means the the operations which may be undone (reversed in a Ctrl-Z sense of a word).

At the minimum an abstract command interface defines the Execute() method. If we go beyond the minimum, the command interface may also define an Undo() method. If an Execute() arithmetically adds a number to its receiver, then the Undo() would subtract the same number from the total, for example.

The invoker may keep a list of the past executed commands, instead of discarding each command as soon as it has executed. The invoker can call Undo() on past commands.

Good discussion here.

The examples in the GoF book don't have the Undo() methods.

2

I think its difficult to understand the pattern without looking at code. How about commanding a Human to Walk around a Walking Track?

Here is an implementation of a Command Pattern:

class Walk
{
    Walk(WalkingTrack track);

    Location walk(Human human);
}

This is simply a Lambda Function for languages without lambda functions.

Here is another implementation of the command patttern:

class Walk
{
    Walk(WalkingTrack track);

    Walking walk(Human human);
}
class Walking
{
    Location location();
    bool completed();
    void cancel();
}

This is a cancel-able command. It is by nature asynchronous, and the caller (or whomever has access to the returned Walking object) can request the process to be stopped, or otherwise monitor its status and perhaps its progress.

An undoable version of the above command would require that calling cancel() also undoes any side-effects introduced by the Walking processing the command. Essentially making the Walking class a form of Transaction.

Here is another Cancelable Command:

class Walk
{
    Walk(WalkingTrack track);

    Location location();

    bool walk(Human human);
    bool walking();
    bool completed();
    void cancel();
}

Try to avoid this layout though. It mixes several layers of concern which clouds its purpose. It also makes it easier for usage mistakes to be accepted by the compiler.

1

What does the author mean by undoable operations? Hard to make out what he actually wants to refer here. I would appreciate a clarification.

He means just rolling back changes. I'm assuming you understand undos/redos and how they work to users just by virtue of using software in general (ex: ctrl-Z to undo a mistake you made). I might be wrong but I interpreted the question as more about how the command pattern facilitates undoing.

With the command pattern you have this object and abstraction between the invoker who wants to "do something" and the receiver who directly does it. As a basic example you might have an image software where the user wants to blur an image.

With the command pattern you'd have this sort of "Blur Image" command object with an abstract interface that all commands have in common, and so you have this command object to execute between the code that wants to blur an image and the code that actually does it. And that provides breathing room in the middle of these two to centralize functionality like logging all commands that are executed.

And likewise if the abstract command interface provides methods like undo/redo, then specific command subtypes can implement/override those functions to undo and redo whatever changes they make in their execution. Then instead of discarding the command object, the system can push it to the history ("undo stack") and start invoking those undo/redo methods as the user moves around in the history, e.g.

With that aside, you don't actually have to have undo/redo functions in the command interface to get undo/redo functionality in ways that still benefit from the command pattern (I wanted to pitch in primarily to mention this point). Again you have this central command execution area between invoker and receiver, so alternatively you can capture your application state before execution of any command (which would alter that state) and push that data (program state) to the undo stack (perhaps in a compacted form) prior to executing any command, and that likewise facilitates undoing/redoing even though the commands themselves don't provide undo/redo functionality.

You have this sort "sandwich". If the invoker and receiver are like two pieces of bread, the command pattern gives you room to put all kinds of stuff in the middle of requesting to perform some operation, like centralized logging, later you might want to uniformly execute commands in a deferred async fashion, you might want to capture program state prior to execution of any command for undoing, you might want to execute the command in a separate process so that if it crashes, the host application doesn't crash, etc. etc. etc. Whereas if the invoker directly invoked functions in the receiver (ex: blurring images directly with no command in the middle), you don't have that centralized breathing room in the middle to uniformly add and do whatever you want.

And specifically by virtue of commands being abstract objects which encapsulate their execution parameters, you can keep them around and execute them at a later time, re-execute them, etc, long after the receiver made the original request. You can also serialize them cheaply. If your program state is uniformly altered by commands, then that state might span gigabytes but the entire user's command history might be capable of being stored in kilobytes, and you can reevaluate those commands and get back to exactly the same state the user was in by just executing those same commands again with the same parameters and order the user originally invoked them.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.