Composite of Commands Design Pattern

2019-07-02 11:44发布

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.

2条回答
等我变得足够好
2楼-- · 2019-07-02 12:22

I'm trying to understand this pattern myself, and have been mulling over what things might be modeled this way.

The basic idea of the Composite pattern is situations where items and collections need to be treated, in some respects, the same way. Collections may contain a mixture of items and subcollections, nested as deeply as you like.

Some ideas I have (borrowing some from Design Patterns in Ruby and Ruby Under a Microscope):

A filesystem

You can ask a file for its size, and it returns a simple value. You can also ask a folder for its size, and it return the sum of the sizes of its files and subfolder. The subfolders are of course returning the sum of their files and subfolders.

Likewise, both files and folders can be moved, renamed, deleted, backed up, compressed, etc.

A system command

A command object could have a run method. That method could work by running any number of subcommands, which have subcommands, etc. It could return true if all of its subcommands return true, and could report statistics based on its childrens' statistics (time elapsed, files modified, etc).

A company hierarchy

Individuals, teams, divisions, and whole companies could all be seen as having salaries, bringing in revenue, completing units of work, etc.

A military unit

In a game, a soldier could have defense and offense statistics, could be told to move to a location, attack a base, etc. Regiments and divisions could be treated the same way.

Weight or monetary value

The weight of a truck full of boxes includes the weight of each box. The weight of each box includes the weight of each of its items, their parts, etc.

Similarly, the monetary value of a financial portfolio is the value of all its assets. Some of those assets might be index funds that contain multiple stocks. You could buy or sell an individual stock or an entire portfolio.

GUI elements

A window could be composed of frames, which are composed of frames, which are composed of frames. Any element can be positioned, shaded, focused, hidden, etc.

Expressions in a programming language

When the Ruby interpreter evaluates an expression, it does so by breaking it down into a tree of expressions (an Abstract Syntax Tree) and evaluating each of those, coming to the final value by working its way back to the top of the tree. In a sense, each level of the tree is asked the same question: "what is your value?"

As a simple example, the first step in finding the value of ((4 + 8) * 2)) + 9 is to find the value of 4 + 8.

查看更多
成全新的幸福
3楼-- · 2019-07-02 12:23

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
查看更多
登录 后发表回答