I have been using the command pattern for quite some time but I’m never really sure how much logic I can actually put in the Execute
method.
My current implementation of the command pattern looks similar to this:
public abstract class Command
{
public static event EventHandler Completed = delegate { };
public bool Success { get; private set; }
public Exception Exception {get; private set; }
public abstract bool Execute();
protected bool OnCompleted(bool success, Exception ex = null)
{
Success = success;
Exception = ex;
Completed(this, EventArgs.Empty)
return success;
}
}
and these are the questions that I ask myself (and practice in my commands):
- Is it OK to show messege boxes or file open dialogs etc.?
- Is it OK to set properties of any object?
- Can a command contain business logic?
- Can a command modify GUI controls in anyway?
- Which layer commands belong to? View or data layer? Can I have commands in both layers?
- Can a command do all that what previously was in the
button1_Click
? - Should a command by unit-testable?
- Can a command be seen as a user that makes use of the APIs and that builds the last layer of an application and also the last resort of catching exceptions?
- Can commands be executed also by code (command calls api, api runs and finally some other api calls a command) or can only the user invoke it and the APIs must not know about ther existence?
- Is there a place for commands in MVC or MVVC or any other design pattern with a controller? They seem to be mutually exclusive. What’s preferable?
There are plenty of tutorials showing how to implement the command pattern but none actually discusses how to apply it in a real wold application.
Command Pattern is generally used to decouple WHAT from WHO, and WHAT from WHEN. This is the benefit of having a simple interface as simple as:
public abstract class Command {
public abstract void execute();
}
Lets imagine that you have a class EngineOnCommand
. You can pass this Command to other objects which accept instances of Command. So this means that the class which receives this EngineOnCommand, could also receive a different Command to be executed by it and it should never know it. This means you decoupled WHAT from WHO.
Now, the second case for the Command Patterm is to decouple WHAT from WHEN. Let’s imagine that you want to create a system in which the actions in the Database should only be executed at night, but following the sequence in which they were requested. With a queue of Commands in which you can execute one by one you could have achieved this. The idea is that the invocation of the command actually only triggers a enqueue of the command on a list to be executed later.
I hope my examples above helps to understand what is the idea of the pattern. Now I will try to answer some of your questions using the above as basis.
Can a command contain business logic?
yes, I believe it should contain it. It it will contain it in a decoupled way of WHO and WHEN.
Can a command modify GUI controls in anyway?
This means you are coupling it to the GUI. This means it is not being used for the intent of decoupling WHAT from WHO. The command ideally should not know who is invoking it, if it is GUI or a batch functionality.
Should a command by unit-testable?
It should not, it must be unit testable. All the code should be unit testable, but ideally all commands must be unit testable.
Sorry for not answering all your questions, but I believe you should check the GOF book. It contains some good examples.
1
Most of the benefit of commands is that they make it easy to undo an action, redo an action, perform an action in multiple places (over a network connection), or perform it at a later date, and so on.
With that in mind:
-
Probably not. It’s hard to imagine any situation where “undoing” a dialog box makes sense, and this is the sort of thing I’d rather put in the UI code.
-
As long as that object is accessible from everywhere that may want to execute the command, then of course it’s fine to change its properties.
-
Absolutely. This ties into whether your model should “contain” business logic; it’s actually considered an anti-pattern (“anemic domain model”) to have too little business logic in there.
-
If the GUI controls are part of your model, absolutely. If not, it’s questionable.
-
Definitely not the view. The view should be informed that the data or the model has changed and react accordingly, but the actual change should take place in the data or the model.
-
In principle, probably yes. Being able to undo commands is one of the best things about the pattern.
-
The execute() function of the command definitely should be unit tested. Again, if commands are tied to a model of some kind, they’re among the easiest parts of an application to test.
-
I’m not quite sure what you mean, but:
- It’s perfectly fine for an external API to use commands as input or output. Assuming you validate them, of course.
- “last layer” sounds like a View to me. In MVC, the commands probably should be generated by the controller and handled by the model, so it would be sensible to say the view is built “above” the command stuff, if that’s what you were going for.
- As for exceptions, probably not. In MVC, I’d expect the controller to be the one catching exceptions and turning them into the right kind of dialog box. Not the model/commands.
-
Absolutely. Most of the benefits of commands are kind of impossible to implement if code never executes them.
-
It’s probably obvious by now, but I do not see them as mutually exclusive at all. On my team at work, the controller translates user-generated events into commands, the model receives and executes the commands (and saves “undo” commands somewhere), and when it’s done the controller tells the view to update itself based on the new model. The commands also get sent over the network to any copies of the model being viewed by other users, so they see it as well. It works brilliantly.
And the title question: How much logic to put in one command? I wouldn’t put any minimum or maximum on it. What I would say is that it’s a very good idea to implement more complex commands using a series of simpler commands, and refactor commands the same way you’d refactor functions.