3

In a document editor application, is it better to implement the command pattern as a layer on top of the object model, or to deeply integrate it into the object model?

If commands are a layer on top, they would manipulate the object model using its regular getters and setters.

  • Pros: Commands are higher level, representing user actions that may involve various low-level model mutations. The model doesn't have to know about commands.
  • Cons: Object model mutators should have as little side effects as possible so that the commands know how to undo their effects.

If commands are deeply integrated, every object model mutator would automatically record an undo action for their effect.

  • Pros: Easier to guarantee that every mutation is correctly undoable.
  • Cons: Commands are lower-level than user actions, so there needs to be a begin/end signal to aggregate any model mutation into the current user-level action for undoing.

Or maybe a third option is to use the observer pattern to listen for low-level model mutations like property changes and record them into undoable commands, aggregated by user action?

Most command pattern articles online don't mention these architectural considerations. I'm looking for real-life experiences, open source software examples that support one of these approaches or articles/books that deal with the topic more in-depth.

1
  • The only feasible way is to have it as a "layer on top" - with a model which records state, and then commands written on top which mutate the model in ways that are valid. Getters and setters should have as few side effects as possible, as otherwise the model risks becoming a Rube-Goldberg machine where the operator presses one button and non-obvious changes occur all over the place. The commands do the heavy lifting of ensuring the model is in a consistent state after each command is applied.
    – Steve
    Nov 7, 2021 at 17:18

1 Answer 1

3

I've tried variations of all the options you laid out and I've had the most success with the third option. If you can decompose all model updates into a set of well-defined primitives (property changes and collection changes typically cover the majority of these updates), then you effectively get undo/redo for free without having to implement custom logic for each command.

The other benefit of this approach is the decoupling of commands and the changes they produce, which are typically coupled in the textbook command pattern. I think of "commands" as UI actions that belong in the application layer: they receive the active document as input, and might obtain additional input from the user before updating the model. The model emits a set of "changes" in response, which by themselves are another form of the traditional command pattern in the sense that they encapsulate undo/redo behavior on a model object.

As for real-world examples, IDEs have some of the most flexible command frameworks out there and most of them are open source. Have a look at the Eclipse command APIs or the command handlers in Roslyn (which provides most of the .NET functionality in Visual Studio). These platforms typically follow a similar approach where all changes are modeled as simple updates to an AST or a text buffer.

2
  • Also, it's hard to have a command undo itself. consider a destructive command, or a command which you update at some point in time changing the functionality and hence invalidating the undo for old versions of the command.
    – Ewan
    Nov 8, 2021 at 0:10
  • Your answer goes exactly along my more recent line of thinking: uniformize model changes as property and collection changes such that undo/redo can be implemented via an observer. I'll definitely look up your links!
    – Trillian
    Nov 10, 2021 at 22:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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