~ It’s about the hierarchy or tree system, that has composite and leaf nodes.
The GoF called the design pattern for our “the sum acts like one of the parts” situation the Composite pattern. We need to use the Composite pattern when you are trying to build a hierarchy or tree of objects, and you do not want the code that uses the tree to constantly have to worry about whether it is dealing with a single object or a whole bushy branch of the tree.
To build the Composite pattern, you need three moving parts.
# First: an abstract base class in the sense that it is not really complete:
# It just keeps track of the name of the task and has a do-nothing get_time_required method.
class Task
attr_reader :name
def initialize(name)
@name = name
end
def get_time_required
0.0
end
end
# Second: simple subclasses of Task
class AddDryIngredientsTask < Task
def initialize
super('Add dry ingredients')
end
def get_time_required
1.0 # 1 minute to add flour and sugar
end
end
class MixTask < Task
def initialize
super('Mix that batter up!')
end
def get_time_required
3.0 # Mix for 3 minutes
end
end
# Third: Define all of the basic cake-baking tasks
class MakeBatterTask < Task
def initialize
super('Make batter')
@sub_tasks = []
**add_sub_task( AddDryIngredientsTask.new )
add_sub_task( AddLiquidsTask.new )
add_sub_task( MixTask.new )**
end
def add_sub_task(task)
@sub_tasks << task
end
def remove_sub_task(task)
@sub_tasks.delete(task)
end
def **get_time_required**
time=0.0
@sub_tasks.each {|task| time += task.get_time_required}
time
end
end
We can make the composite and leaf objects different. For example, we can supply the composite object with add_child and remove_child methods and simply omit these methods from the leaf class. This approach has certain logic behind it: After all, leaf objects are childless, so they do not need the child management plumbing.
On the other hand, our main goal with the Composite pattern is to make the leaf and composite objects indistinguishable. If the code that uses your composite has to know that only some of the components—the composite ones—have get_child and add_child methods while other components—the leaves—do not, then the leaf and composite objects are not the same. But if you do include the child-handling methods in the leaf object, what happens if someone actually calls them on a leaf object? Responding to remove_child is not so bad—leaf objects do not have children so there is never anything to remove. But what if someone calls add_child on a leaf object? Do you ignore the call? Throw an exception? Neither response is very palatable.
As I say, how you handle this decision is mostly a matter of taste: Make the leaf and composite classes different, or burden the leaf classes with embarrassing methods that they do not know how to handle. My own instinct is to leave the methods off of the leaf classes. Leaf objects cannot handle child objects, and we may as well admit it.
So far, we have looked at the Composite pattern as a strictly top-down affair. Because each composite object holds references to its subcomponents but the child components do NOT know a thing about their parents, it is easy to traverse the tree from the root to the leaves but hard to go the other way.
It is easy to add a parent reference to each participant in the composite so that we can climb back up to the top of the tree.
# Interface: base class
class Task
attr_accessor :name, **:parent**
def initialize(name)
@name = name
**@parent = nil**
end
end
# Leaf: subclasses
class CompositeTask < Task
def initialize(name)
super(name)
@sub_tasks = []
end
def add_sub_task(task)
@sub_tasks << task
**task.parent = self**
end
def remove_sub_task(task)
@sub_tasks.delete(task)
**task.parent = nil**
end
end
# With the parent references in place, we can now trace any composite component
# up to its ultimate parent:
**while** task
puts("task: #{task}")
**task = task.parent**
end