Just to extend a little bit on Robert Harvey's already excellent answer in a way that might click a bit more with you:
Isn't this functionality achievable with a simple interface? In all the other, more general books the main benefits of this pattern are: postponing the execution, queuing commands, logging and undo functionality.
In that case you are directly utilizing the receiver instead of having this command abstraction in the middle.
Let's say I crate an ICommandable interface instead of using the commands. How is that different functionally? I know that it's different structurally: I'm completely omitting the [Command object] part, but I can't see what's the decoupling advantage here. In both cases I need to know what's the receiver, when creating the command/calling the interface function. Am I missing something?
Firstly if you want to introduce new commands to your system, then it would require rather intrusive centralized changes to this ICommandable
interface. Moreover each new function you add to that interface would have to be implemented by every concrete Commandable
that implements that interface which could make such extensions particularly costly if you have more than one of these.
Secondly it doesn't allow you to sandwich new functionality between invoker and receiver. Undoing and logging and queuing and parallelizing are just some examples of the kind of functionality you might want to add (and even possibly in hindsight) between invoker and receiver.
In my case on top of all these things, we also have commands being registered by third parties whose plugins are loaded by users at runtime (we use the factory pattern to allow such registration and instantiation of commands by name). The registration of a new command causes that command to be displayed in the UI automatically as well as now being accessible to both the native code and our embedded scripting language. It allows that command to be invoked in various ways by users from clicking on GUI elements or typing in commands into the "command" (scripting) console or writing a script in their own file and adding that as a plugin or writing a C++ plugin and building it and loaded it dynamically. You probably don't need all these bells and whistles but they're additional examples of what sort of rich behaviors you can get with the command pattern when you combine it with factory.
But also going back to functionality you can sandwich in between, in our case since plugins written by third parties (who might not always write the most sound code) can register commands for execution, some poorly-written plugins might have a tendency to crash bringing our host application down in flames along with it. So we actually launch a separate process and execute the command there with IPC to synchronize changes if that command succeeds. That has had the practical effect of making our host application almost "crash-proof".
And that's just another example of functionality you can sandwich in between invoker and receiver. It is very useful in my experience and provides a lot of breathing room that way for what I consider a rather trivial upfront cost (though I might think of it as trivial partially because I've been using this basic pattern in some form or another for as far back as I can remember).