Command pattern command is an instruction to do something, something specific. It can be execute right now or later or specific time. In our GUI's do and undo actions it is wildly used.
If we have a Button base class:
class SlickButton # # Lots of button drawing and management # code omitted... # def on_button_push # # Do something when the button is pushed # end end
There are many buttons that inherit from the SlickButton to overwrite the on_button_push method.
If some of the button's action is dynamically changing, the subclass will be an explosive increasing.
If we use Command:
class SlickButton attr_accessor :command def initialize(command) @command = command end # # Lots of button drawing and management # code omitted... # def on_button_push @command.execute if @command end end
The base command
class SaveCommand def execute # # Save the current document... # end end
Then the command is seperate from button, and the button can have any command they want even in runtime.
We can define many commands such as: CreateFile, DeleteFile, CopyFile and so on.
We can also build Composite command use Composite pattern:
class CompositeCommand < Command def initialize @commands = [] end def add_command(cmd) @commands << cmd end def execute @commands.each {|cmd| cmd.execute} end def description description = '' @commands.each {|cmd| description += cmd.description + "\n"} description end end
cmds = CompositeCommand.new cmds.add_command(CreateFile.new('file1.txt', "hello world\n")) cmds.add_command(CopyFile.new('file1.txt', 'file2.txt')) cmds.add_command(DeleteFile.new('file1.txt'))
The undo command is really common, the Command pattern support it by define a undo method in the command class:
class CreateFile < Command def initialize(path, contents) super "Create file: #{path}" @path = path @contents = contents end def execute f = File.open(@path, "w") f.write(@contents) f.close end def unexecute File.delete(@path) end end
class DeleteFile < Command def initialize(path) super "Delete file: #{path}" @path = path end def execute if File.exists?(@path) @contents = File.read(@path) end f = File.delete(@path) end def unexecute if @contents f = File.open(@path,"w") f.write(@contents) f.close end end end
The composite command will be :
class CompositeCommand < Command # ... def unexecute @commands.reverse.each { |cmd| cmd.unexecute } end # ... end
The key thing about the Command pattern is that it separates the thought from the deed.
When you use this pattern, you are no longer simply saying, “Do this”; instead, you are saying, “Remember how to do this,” and, sometime later, “Do that thing that I told you to remember.”
Even in the lightweight code block-based renditions of the Command pattern available in Ruby, the two-part aspect of this pattern adds some serious complexity to your code.
Make sure that you really need that complexity before you pull the Command pattern out of your bag of tricks.
The best example of the Command may be the ActiveRecord Migrations with the self.up and self.down method that enable database level do migrate and undo migrate.
It it much easy to do database migration, the latest version of rails can support specific migration do and undo by recording all the migrate number.
Another great example is Madeleine