Composite
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.
Participants
| Role | In this example | Responsibility |
|---|---|---|
| Component | #lines | The interface shared by single widgets and groups. |
| Leaf | Label | A widget with no children. It produces the actual text. |
| Composite | Group | Holds widgets (labels or more groups) and combines their results. |
| Client | Caller of | Doesn't need to know whether it holds one label or the whole sidebar. |
Ruby implementation
# The Leaf: one line of text in the sidebar.
class Label
def initialize(text)
@text = text
end
def lines = [@text]
end
# The Composite: a group of widgets, any of which may itself be a group.
# It answers the same messages as a Label, so the UI treats both alike.
class Group
include Enumerable
def initialize(*widgets)
@widgets = widgets
end
def add(widget)
@widgets << widget
self
end
def each(&block)
@widgets.each(&block)
end
def lines
flat_map(&:lines)
end
def height
lines.size
end
endIn use
towers = Group.new(Label.new("Towers"), Label.new("1 Arrow 50g"), Label.new("2 Frost 70g"))
sidebar = Group.new(Label.new("Gold: 120"), Label.new("Lives: 20"), towers)
sidebar.lines
# => ["Gold: 120", "Lives: 20", "Towers", "1 Arrow 50g", "2 Frost 70g"]
sidebar.height # => 5
towers.height # => 3
Notes
Ruby idioms
include Enumerableand define#each. The composite then getsflat_map,select,countand the rest for free.#linesbecomesflat_map(&:lines).- Let the leaf answer the group’s question too.
Label#linesreturns a one-element array, soflat_map(&:lines)works however deep the tree goes, with no type checks.
Reach for it when
- Your data is a tree: UI widgets, menus, files and folders, an organisation chart.
- Clients would otherwise be full of
if widget.is_a?(Group)checks.
Watch for
- Uniformity can hide meaningful differences. If
#addmakes no sense on a leaf, leave it off the leaf. Don’t add a method that raises just to keep the interfaces identical.
One label or the whole sidebar: both answer the same question.