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 callsetCommandon invoker - Order <-->
Command - Waitress <-->
Invoker: can callexecuteon 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
executemethod - 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
NoCommandcan be useful (no need to checknullany more). aka "null object" (better handled byOptiontype in scala) - More usages:
- Job queues: have a group of threads sit on end of the queue, retrieve a command object, call its
executemethod. - Logging requests: as we execute commands, we store a history of them on disk (command object supports
savemethod). When a crash occurs, we reload the command objects (reloadmethod of command object) and invokeexecutein 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