Posted almost 5 years ago. Visible to the public.

Command Pattern

This pattern a simple but deceptively powerful technique that decouples events — such as user interface interactions — from the concrete actions those events should trigger. The command in the command pattern couldn’t be simpler: it is the combination of a thing and an action. It’s an object and a method to be invoked on that object. That’s it! By itself, a command doesn’t count as a Pattern™. It’s just another object. What makes the pattern is the “how” and the “why” it gets used. “How” command objects get used is via a set of similar objects. These invoker objects are usually (but are not limited to) UI elements—think buttons or menu items. And they need to be able to tell another object to do something. The pattern arises from a natural desire for consistency. You don’t want one button to call a function while another invokes a command via a call() method while a third uses a do() method. That’s silly. The common sense desire for all buttons to invoke their commands in the same way is what gives rise to the pattern. Consistency is not the sole benefit of the command pattern. Once you have a command object that can tell a series of objects to do something, it is a short distance to being able to tell those same objects to undo those actions. It also gets easier to combine actions into macro commands.

Copy
# Pattern name: receiver class Robot def initialize(x = 0, y = 0) @x, @y, = x, y end def location "x[#{@x}],y[#{@y}]" end def move_right @x += 1 end def move_left @x -= 1 end def move_up @y += 1 end def move_down @y -= 1 end end # Pattern name: invoker class Button def initialize(name, command) @name, @command = name, command end def press @command.() end end def start puts '*** Robot Stuffs ***' r = Robot.new puts "Robot starts at: #{r.location}" button_right = Button.new('Right', r.method(:move_right)) button_left = Button.new('Left', r.method(:move_left)) button_up = Button.new('Up', r.method(:move_up)) button_down = Button.new('Down', r.method(:move_down)) button_right.press puts "Robot current location: #{r.location}" button_left.press puts "Robot current location: #{r.location}" button_up.press puts "Robot current location: #{r.location}" button_down.press puts "Robot current location: #{r.location}" puts '---' puts "Robot ends at: #{r.location}" end

The fun stuff with the command pattern starts with undoing commands. After pressing the “Up” button, we ought to be able to hit the “Undo” button to return things to their previous state.

Copy
# Pattern name: command module Command def call end def undo end end class MoveRight include Command def initialize(receiver) @receiver = receiver end def call @receiver.move_right end def undo @receiver.move_left end end class MoveLeft include Command def initialize(receiver) @receiver = receiver end def call @receiver.move_left end def undo @receiver.move_right end end class MoveUp include Command def initialize(receiver) @receiver = receiver end def call @receiver.move_up end def undo @receiver.move_down end end class MoveDown include Command def initialize(receiver) @receiver = receiver end def call @receiver.move_down end def undo @receiver.move_up end end class History class << self def stack @stack ||= [] end def add(command) stack << command end def undo command = stack.pop command.undo puts "Undoing #{command.class} command" end def undo_all stack.each do |command| command.undo puts "Undoing #{command.class} command" end end end end # Pattern name: invoker class Button def initialize(name, command) @name, @command = name, command end def press History.add @command @command.call end end def start puts '*** Robot Stuffs ***' r = Robot.new puts "Robot starts at: #{r.location}" move_right = MoveRight.new r move_left = MoveLeft.new r move_up = MoveUp.new r move_down = MoveDown.new r button_right = Button.new('Right', move_right) button_left = Button.new('Left', move_left) button_up = Button.new('Up', move_up) button_down = Button.new('Down', move_down) button_right.press puts "Robot current location: #{r.location}" button_right.press puts "Robot current location: #{r.location}" button_right.press puts "Robot current location: #{r.location}" button_left.press puts "Robot current location: #{r.location}" button_up.press puts "Robot current location: #{r.location}" button_down.press puts "Robot current location: #{r.location}" History.undo History.undo_all puts '---' puts "Robot ends at: #{r.location}" end

Owner of this card:

Avatar
Alexander M
Last edit:
almost 5 years ago
by Alexander M
Tags:
Software-Architecture
Posted by Alexander M to Ruby and RoR knowledge base
This website uses short-lived cookies to improve usability.
Accept or learn more