Design Pattern #6: Command Pattern
These are my notes about the book Head First Design Patterns.
Example problem
Home automation remote control: on, off, undo. Supporting extensible commands.
Cook example
- Customer <-->
Client
: customer create command object and callsetCommand
on invoker - Order <-->
Command
- Waitress <-->
Invoker
: can callexecute
on the command objects. - Cook <-->
Receiver
class Order(cook:Cook) { // hold reference to the actual "actor" def orderUp() : Unit = { // interface that encapsulates the action cook.makeBurger // can vary among different command objects } }
Simple remote control with one slot
abstract class Command { def execute:Unit } class LightOnCommand(light:Light) extends Command{ // command object def execute = light.on } class RemoteControl{ // invoker var slot:Command = null def setCommand(cmd:Command):Unit = slot = cmd def buttonPressed:Unit = slot.execute } object Main{ // client def main(args:Array[String]):Unit = { val control = new RemoteControl control.setCommand(new LightOnCommand(new Light)) control.buttonPressed } }
Support undo:
Keep track of last command called.
// NOTE: these are NOT gramatically correct abstract class Command { def execute:Unit def undo:Unit } class LightOnCommand(light:Light) extends Command{ // command object def execute = light.on def undo = light.off } class RemoteControl{ // invoker val slots:Seq[Command] = Nil var undoCommand:Command = NoCommand def setCommand(i:Int, cmd:Command):Unit = slots[i] = cmd def buttonPressed(i:Int):Unit = { slots(i).execute undoCommand = slots(i) // keep track of last command } def undo = undoCommand.undo }
To support a history of undos, keep a stack.
Use a macro command
class MacroCommand(commands:Seq[Command]) extends Command { def execute = commands.foreach(_.execute) }
Definition
- Encapsulating a request as an object. Decouple the requester of an action from the object that actually performs the action.
- "Store them, pass them around, and invoke them when you need them"
- Roles:
- Client: create a concrete command and set its receiver
- Invoker: holds a command and call its
execute
method - Receiver: know how to carry out the request
- Command: provide
execute
(andundo
) method. Bind together a set of actions on a specific receiver.
Others
- Implementing a
NoCommand
can be useful (no need to checknull
any more). aka "null object" (better handled byOption
type in scala) - More usages:
- Job queues: have a group of threads sit on end of the queue, retrieve a command object, call its
execute
method. - Logging requests: as we execute commands, we store a history of them on disk (command object supports
save
method). When a crash occurs, we reload the command objects (reload
method of command object) and invokeexecute
in order. --> can support all-or-nothing transaction.
- Job queues: have a group of threads sit on end of the queue, retrieve a command object, call its
Comments
Comments powered by Disqus