Search code examples
rubyoopdesign-patternsooad

Composite of Commands Design Pattern


Does anyone have a good example in Ruby of using a Composite of Commands? It is a design pattern hybrid that I have seen mentioned in various Design Patterns literature which sounds quite powerful, but have not been able to find any interesting use cases or code.


Solution

  • Inspired by the general idea and the sample pattern implementations in this blog post, here's a stab at what it might look like:

    class CompositeCommand
      def initialize(description, command, undo)
        @description=description; @command=command; @undo=undo
        @children = []
      end
      def add_child(child); @children << child; self; end
      def execute
        @command.call() if @command && @command.is_a?(Proc)
        @children.each {|child| child.execute}
      end
      def undo
        @children.reverse.each {|child| child.undo}
        @undo.call() if @undo && @undo.is_a?(Proc)
      end
    end
    

    And sample usage using the application of a software installer program:

    class CreateFiles < CompositeCommand
      def initialize(name)
        cmd = Proc.new { puts "OK: #{name} files created" }
        undo = Proc.new { puts "OK: #{name} files removed" }
        super("Creating #{name} Files", cmd, undo)
      end
    end
    
    class SoftwareInstaller
      def initialize; @commands=[]; end
      def add_command(cmd); @commands << cmd; self; end
      def install; @commands.each(&:execute); self; end
      def uninstall; @commands.reverse.each(&:undo); self end
    end
    
    installer = SoftwareInstaller.new
    installer.add_command(
      CreateFiles.new('Binary').add_child(
        CreateFiles.new('Library')).add_child(
        CreateFiles.new('Executable')))
    installer.add_command(
      CreateFiles.new('Settings').add_child(
        CreateFiles.new('Configuration')).add_child(
        CreateFiles.new('Preferences')).add_child(
        CreateFiles.new('Help')))
    installer.install # => Runs all commands recursively
    installer.uninstall