CrackedRuby CrackedRuby

Overview

The Composite Pattern organizes objects into tree structures where individual objects and groups of objects share the same interface. This pattern addresses the problem of building hierarchical structures where both primitive elements and containers of elements must respond to the same operations. The pattern originates from the Gang of Four design patterns catalog and solves the complexity of handling nested structures by treating all nodes in the tree uniformly.

The pattern defines three primary roles: Component (the common interface), Leaf (primitive objects that cannot contain other objects), and Composite (container objects that hold other components). Clients interact with the component interface without knowing whether they work with a single object or a composition of objects. This eliminates conditional logic that checks object types and simplifies client code when working with recursive structures.

# Basic structure showing uniform treatment
class FileSystemItem
  def size
    raise NotImplementedError
  end
end

class File < FileSystemItem
  def initialize(name, bytes)
    @name = name
    @bytes = bytes
  end

  def size
    @bytes
  end
end

class Directory < FileSystemItem
  def initialize(name)
    @name = name
    @children = []
  end

  def add(item)
    @children << item
  end

  def size
    @children.sum(&:size)
  end
end

# Client treats both uniformly
root = Directory.new("root")
root.add(File.new("a.txt", 100))
root.add(File.new("b.txt", 200))

puts root.size  # => 300

The pattern applies to domains where hierarchical structures naturally occur: file systems, organizational charts, graphical user interfaces, arithmetic expressions, and document structures. The uniform interface enables recursive algorithms to traverse and process entire structures without type checking at each level.

Key Principles

The Composite Pattern operates on three fundamental principles. First, the Component abstraction defines operations that apply to both simple and complex objects. This abstraction establishes the contract that all participants in the structure must fulfill. Second, recursive composition allows Composites to contain both Leaf objects and other Composites, creating arbitrary depth hierarchies. Third, location transparency means clients interact with the structure through the component interface without distinguishing between leaves and composites.

The Component interface declares operations for both leaf and composite behaviors. Common operations include adding and removing child components, accessing children, and performing the primary domain operations. Some operations make sense only for composites (like add or remove), while others apply to all nodes (like size or display). The design must address how to handle operations that do not apply to all node types.

# Component interface with both types of operations
module Component
  def operation
    raise NotImplementedError
  end
  
  def add(component)
    raise NotImplementedError, "#{self.class} cannot add children"
  end
  
  def remove(component)
    raise NotImplementedError, "#{self.class} cannot remove children"
  end
  
  def get_child(index)
    raise NotImplementedError, "#{self.class} has no children"
  end
end

Leaf objects represent terminal nodes in the structure. They implement the component interface but typically raise errors or return empty results for composite-specific operations. Leaf objects perform the actual work of the domain operations since they represent primitive elements that cannot delegate to children.

Composite objects implement component operations by delegating to their children. When a composite receives an operation request, it forwards the request to each child and may combine the results. This recursive delegation continues until reaching leaf nodes. Composites maintain a collection of child components and provide methods to add, remove, and access children.

# Leaf behavior - performs actual work
class TextElement
  include Component
  
  def initialize(text)
    @text = text
  end
  
  def operation
    "Text: #{@text}"
  end
end

# Composite behavior - delegates to children
class Container
  include Component
  
  def initialize(name)
    @name = name
    @children = []
  end
  
  def operation
    results = @children.map(&:operation)
    "Container #{@name}: [#{results.join(', ')}]"
  end
  
  def add(component)
    @children << component
  end
  
  def remove(component)
    @children.delete(component)
  end
  
  def get_child(index)
    @children[index]
  end
end

# Building and operating on structure
root = Container.new("root")
root.add(TextElement.new("A"))

branch = Container.new("branch")
branch.add(TextElement.new("B"))
branch.add(TextElement.new("C"))
root.add(branch)

puts root.operation
# => Container root: [Text: A, Container branch: [Text: B, Text: C]]

The pattern supports two design approaches for the component interface. The transparent approach declares all operations (including add, remove, get_child) in the component interface, making leaves and composites fully interchangeable but requiring leaves to implement operations that do not make sense for them. The safe approach declares only operations applicable to all nodes in the component interface, with composite-specific operations defined only in the Composite class, providing type safety at the cost of losing uniform treatment for structural operations.

Ruby Implementation

Ruby implements the Composite Pattern using modules for the component interface and classes for leaf and composite implementations. Modules define the common interface through method declarations that raise NotImplementedError, establishing the contract without inheritance constraints. Classes include the module and provide concrete implementations.

module GraphicComponent
  def draw
    raise NotImplementedError
  end
  
  def bounds
    raise NotImplementedError
  end
end

class Circle
  include GraphicComponent
  
  def initialize(x, y, radius)
    @x, @y, @radius = x, y, radius
  end
  
  def draw
    "Drawing circle at (#{@x}, #{@y}) with radius #{@radius}"
  end
  
  def bounds
    { x: @x - @radius, y: @y - @radius, 
      width: @radius * 2, height: @radius * 2 }
  end
end

class Rectangle
  include GraphicComponent
  
  def initialize(x, y, width, height)
    @x, @y, @width, @height = x, y, width, height
  end
  
  def draw
    "Drawing rectangle at (#{@x}, #{@y}) sized #{@width}x#{@height}"
  end
  
  def bounds
    { x: @x, y: @y, width: @width, height: @height }
  end
end

class Group
  include GraphicComponent
  
  def initialize
    @components = []
  end
  
  def add(component)
    @components << component
    self
  end
  
  def remove(component)
    @components.delete(component)
    self
  end
  
  def draw
    drawings = @components.map(&:draw)
    "Group: [#{drawings.join(', ')}]"
  end
  
  def bounds
    return { x: 0, y: 0, width: 0, height: 0 } if @components.empty?
    
    all_bounds = @components.map(&:bounds)
    min_x = all_bounds.map { |b| b[:x] }.min
    min_y = all_bounds.map { |b| b[:y] }.min
    max_x = all_bounds.map { |b| b[:x] + b[:width] }.max
    max_y = all_bounds.map { |b| b[:y] + b[:height] }.max
    
    { x: min_x, y: min_y, width: max_x - min_x, height: max_y - min_y }
  end
end

# Using the graphics hierarchy
canvas = Group.new
canvas.add(Circle.new(10, 10, 5))
canvas.add(Rectangle.new(20, 20, 30, 40))

nested_group = Group.new
nested_group.add(Circle.new(100, 100, 10))
nested_group.add(Circle.new(120, 100, 10))
canvas.add(nested_group)

puts canvas.draw
puts canvas.bounds.inspect

Ruby's Enumerable module integrates naturally with composite implementations. Composites can include Enumerable and implement the each method to enable iteration over children. This provides access to map, select, reduce, and other iteration methods without additional code.

class CompositeTask
  include Enumerable
  
  def initialize(name)
    @name = name
    @subtasks = []
  end
  
  def add(task)
    @subtasks << task
    self
  end
  
  def each(&block)
    @subtasks.each(&block)
  end
  
  def time_estimate
    @subtasks.sum(&:time_estimate)
  end
  
  def complete?
    @subtasks.all?(&:complete?)
  end
end

class Task
  attr_reader :name, :time_estimate
  
  def initialize(name, time_estimate, complete = false)
    @name = name
    @time_estimate = time_estimate
    @complete = complete
  end
  
  def complete?
    @complete
  end
end

project = CompositeTask.new("Project")
project.add(Task.new("Design", 8))
project.add(Task.new("Implementation", 20))

testing = CompositeTask.new("Testing")
testing.add(Task.new("Unit tests", 5))
testing.add(Task.new("Integration tests", 8))
project.add(testing)

# Using Enumerable methods
puts "Total time: #{project.time_estimate} hours"
puts "Incomplete tasks: #{project.select { |t| !t.complete? }.map(&:name)}"
puts "All complete? #{project.complete?}"

Ruby blocks and procs provide flexible traversal mechanisms for composite structures. Implementing traversal methods that accept blocks allows clients to process nodes during tree walks without knowing the structure's implementation details.

class Node
  attr_reader :name
  
  def initialize(name)
    @name = name
    @children = []
  end
  
  def add(node)
    @children << node
    self
  end
  
  # Pre-order traversal with block
  def traverse_preorder(&block)
    block.call(self)
    @children.each { |child| child.traverse_preorder(&block) }
  end
  
  # Post-order traversal with block
  def traverse_postorder(&block)
    @children.each { |child| child.traverse_postorder(&block) }
    block.call(self)
  end
  
  # Level-order traversal
  def traverse_levelorder(&block)
    queue = [self]
    until queue.empty?
      current = queue.shift
      block.call(current)
      queue.concat(current.instance_variable_get(:@children))
    end
  end
  
  # Find nodes matching condition
  def find_all(&predicate)
    results = []
    traverse_preorder do |node|
      results << node if predicate.call(node)
    end
    results
  end
end

# Building and traversing tree
root = Node.new("root")
root.add(Node.new("a")).add(Node.new("b"))

branch = Node.new("branch")
branch.add(Node.new("c")).add(Node.new("d"))
root.add(branch)

puts "Pre-order:"
root.traverse_preorder { |n| puts "  #{n.name}" }

puts "Post-order:"
root.traverse_postorder { |n| puts "  #{n.name}" }

puts "Find nodes with 'a' in name:"
root.find_all { |n| n.name.include?('a') }.each do |n|
  puts "  #{n.name}"
end

Practical Examples

A menu system demonstrates composite structures where menu items and submenus share a common interface. Individual menu items represent leaf nodes that execute actions when selected. Submenus represent composite nodes that contain other menu items or nested submenus.

class MenuItem
  attr_reader :label
  
  def initialize(label, &action)
    @label = label
    @action = action
  end
  
  def select
    @action.call if @action
  end
  
  def display(indent = 0)
    puts "#{'  ' * indent}#{@label}"
  end
  
  def find(label)
    @label == label ? self : nil
  end
end

class Menu
  attr_reader :label
  
  def initialize(label)
    @label = label
    @items = []
  end
  
  def add(item)
    @items << item
    self
  end
  
  def select
    display
  end
  
  def display(indent = 0)
    puts "#{'  ' * indent}#{@label}"
    @items.each { |item| item.display(indent + 1) }
  end
  
  def find(label)
    return self if @label == label
    @items.each do |item|
      found = item.find(label)
      return found if found
    end
    nil
  end
end

# Building menu hierarchy
main_menu = Menu.new("Main Menu")

file_menu = Menu.new("File")
file_menu.add(MenuItem.new("New") { puts "Creating new file" })
file_menu.add(MenuItem.new("Open") { puts "Opening file" })
file_menu.add(MenuItem.new("Save") { puts "Saving file" })
main_menu.add(file_menu)

edit_menu = Menu.new("Edit")
edit_menu.add(MenuItem.new("Cut") { puts "Cutting" })
edit_menu.add(MenuItem.new("Copy") { puts "Copying" })
edit_menu.add(MenuItem.new("Paste") { puts "Pasting" })
main_menu.add(edit_menu)

main_menu.add(MenuItem.new("Exit") { puts "Exiting application" })

# Using the menu system
main_menu.display

found = main_menu.find("Copy")
found&.select  # => Copying

An arithmetic expression evaluator represents mathematical expressions as trees where numbers are leaves and operators are composites. Each operator node contains operand sub-expressions that may themselves be complex expressions or simple numbers.

class Expression
  def evaluate
    raise NotImplementedError
  end
  
  def to_s
    raise NotImplementedError
  end
end

class Number < Expression
  def initialize(value)
    @value = value
  end
  
  def evaluate
    @value
  end
  
  def to_s
    @value.to_s
  end
end

class BinaryOperation < Expression
  def initialize(left, right)
    @left = left
    @right = right
  end
end

class Addition < BinaryOperation
  def evaluate
    @left.evaluate + @right.evaluate
  end
  
  def to_s
    "(#{@left} + #{@right})"
  end
end

class Multiplication < BinaryOperation
  def evaluate
    @left.evaluate * @right.evaluate
  end
  
  def to_s
    "(#{@left} * #{@right})"
  end
end

class Subtraction < BinaryOperation
  def evaluate
    @left.evaluate - @right.evaluate
  end
  
  def to_s
    "(#{@left} - #{@right})"
  end
end

class Division < BinaryOperation
  def evaluate
    denominator = @right.evaluate
    raise ZeroDivisionError, "Division by zero" if denominator == 0
    @left.evaluate.to_f / denominator
  end
  
  def to_s
    "(#{@left} / #{@right})"
  end
end

# Building expression: ((5 + 3) * 2) - (10 / 2)
left_side = Multiplication.new(
  Addition.new(Number.new(5), Number.new(3)),
  Number.new(2)
)

right_side = Division.new(Number.new(10), Number.new(2))
expression = Subtraction.new(left_side, right_side)

puts expression.to_s  # => (((5 + 3) * 2) - (10 / 2))
puts expression.evaluate  # => 11.0

A document structure with sections, paragraphs, and text elements models hierarchical documents where sections contain other sections or content elements. This structure supports operations like word counting, rendering, and search across the entire document through uniform interfaces.

class DocumentElement
  def word_count
    raise NotImplementedError
  end
  
  def render
    raise NotImplementedError
  end
  
  def search(term)
    []
  end
end

class Text < DocumentElement
  attr_reader :content
  
  def initialize(content)
    @content = content
  end
  
  def word_count
    @content.split.length
  end
  
  def render
    @content
  end
  
  def search(term)
    @content.downcase.include?(term.downcase) ? [self] : []
  end
end

class Paragraph < DocumentElement
  def initialize
    @elements = []
  end
  
  def add(element)
    @elements << element
    self
  end
  
  def word_count
    @elements.sum(&:word_count)
  end
  
  def render
    @elements.map(&:render).join(' ')
  end
  
  def search(term)
    @elements.flat_map { |e| e.search(term) }
  end
end

class Section < DocumentElement
  attr_reader :title
  
  def initialize(title)
    @title = title
    @contents = []
  end
  
  def add(content)
    @contents << content
    self
  end
  
  def word_count
    @contents.sum(&:word_count)
  end
  
  def render
    rendered_contents = @contents.map(&:render).join("\n\n")
    "# #{@title}\n\n#{rendered_contents}"
  end
  
  def search(term)
    results = @title.downcase.include?(term.downcase) ? [self] : []
    results + @contents.flat_map { |c| c.search(term) }
  end
end

# Building document structure
doc = Section.new("Software Design Patterns")

intro = Paragraph.new
intro.add(Text.new("Design patterns provide reusable solutions"))
intro.add(Text.new("to common software design problems."))
doc.add(intro)

subsection = Section.new("Structural Patterns")
para = Paragraph.new
para.add(Text.new("Structural patterns describe how classes"))
para.add(Text.new("and objects combine to form larger structures."))
subsection.add(para)
doc.add(subsection)

puts "Total words: #{doc.word_count}"
puts "\nRendered document:"
puts doc.render
puts "\nSearch for 'patterns':"
doc.search('patterns').each { |e| puts "  Found in: #{e.class}" }

Design Considerations

The Composite Pattern applies when the domain naturally contains hierarchical part-whole relationships and clients should treat individual objects and compositions uniformly. File systems, organizational structures, graphical scenes, and nested data structures benefit from this pattern. The decision to use composites depends on whether the benefit of uniform treatment outweighs the complexity of maintaining tree structures.

The transparent approach places all operations in the component interface, including add, remove, and child access methods. This achieves maximum uniformity since clients can call any operation on any component without type checking. Leaf implementations must handle composite-specific operations, typically by raising errors or returning nil. This approach trades type safety for simplicity in client code.

# Transparent approach - all operations in component
module Component
  def operation
    raise NotImplementedError
  end
  
  def add(component)
    raise "Cannot add to #{self.class}"
  end
  
  def remove(component)
    raise "Cannot remove from #{self.class}"
  end
  
  def children
    []
  end
end

class Leaf
  include Component
  
  def operation
    "Leaf operation"
  end
  
  # Inherits add/remove that raise errors
end

class Composite
  include Component
  
  def initialize
    @children = []
  end
  
  def operation
    @children.map(&:operation).join(', ')
  end
  
  def add(component)
    @children << component
    self
  end
  
  def remove(component)
    @children.delete(component)
    self
  end
  
  def children
    @children.dup
  end
end

# Client code treats all nodes uniformly
def process(component)
  puts component.operation
  component.children.each { |child| process(child) }
end

The safe approach restricts the component interface to operations that make sense for all nodes. Only composites expose structural operations like add and remove. This provides type safety at compile time (or with Ruby's duck typing, at runtime) but requires clients to check types before performing composite-specific operations. This approach prevents errors from calling invalid operations on leaves.

# Safe approach - minimal component interface
module Component
  def operation
    raise NotImplementedError
  end
end

class SafeLeaf
  include Component
  
  def operation
    "Leaf operation"
  end
end

class SafeComposite
  include Component
  
  def initialize
    @children = []
  end
  
  def operation
    @children.map(&:operation).join(', ')
  end
  
  # Structural operations only in Composite
  def add(component)
    @children << component
    self
  end
  
  def remove(component)
    @children.delete(component)
    self
  end
  
  def children
    @children.dup
  end
end

# Client must check type for structural operations
def add_to_composite(target, component)
  if target.respond_to?(:add)
    target.add(component)
  else
    raise "Cannot add to #{target.class}"
  end
end

The pattern compares with the Decorator Pattern, which also uses recursive composition. Decorators focus on adding responsibilities to objects while maintaining the same interface, typically with one child component. Composites focus on representing part-whole hierarchies with potentially many children. The Iterator Pattern often complements composites by providing standard traversal mechanisms without exposing internal structure.

Performance considerations include the overhead of traversing potentially deep hierarchies for operations that aggregate results from all nodes. Caching computed values at composite nodes reduces repeated calculations when the structure remains unchanged. Memory overhead from maintaining parent references or child collections scales with tree size and depth.

Common Patterns

The caching pattern stores computed results at composite nodes to avoid repeated traversal when the structure remains static. Composites invalidate cached values when children change, either immediately or lazily on next access. This pattern significantly improves performance for frequently accessed aggregate values in stable structures.

class CachedComposite
  def initialize(name)
    @name = name
    @children = []
    @cached_size = nil
  end
  
  def add(component)
    @children << component
    invalidate_cache
    self
  end
  
  def remove(component)
    @children.delete(component)
    invalidate_cache
    self
  end
  
  def size
    return @cached_size if @cached_size
    @cached_size = @children.sum(&:size)
  end
  
  private
  
  def invalidate_cache
    @cached_size = nil
  end
end

class Item
  def initialize(size)
    @size = size
  end
  
  def size
    @size
  end
end

container = CachedComposite.new("root")
container.add(Item.new(100))
container.add(Item.new(200))

puts container.size  # Computes: 300
puts container.size  # Returns cached: 300

container.add(Item.new(50))
puts container.size  # Recomputes: 350

The parent reference pattern adds a parent pointer to each component, enabling upward traversal from any node to the root. This supports operations that need context from ancestors, such as computing absolute positions or propagating changes up the tree. Parent references require careful management during add and remove operations to maintain consistency.

class ComponentWithParent
  attr_reader :name
  attr_accessor :parent
  
  def initialize(name)
    @name = name
    @parent = nil
  end
  
  def path
    parts = [name]
    current = parent
    while current
      parts.unshift(current.name)
      current = current.parent
    end
    parts.join('/')
  end
  
  def root
    current = self
    current = current.parent while current.parent
    current
  end
end

class CompositeWithParent < ComponentWithParent
  def initialize(name)
    super
    @children = []
  end
  
  def add(component)
    component.parent = self
    @children << component
    self
  end
  
  def remove(component)
    component.parent = nil
    @children.delete(component)
    self
  end
end

root = CompositeWithParent.new("root")
branch = CompositeWithParent.new("branch")
leaf = ComponentWithParent.new("leaf")

root.add(branch)
branch.add(leaf)

puts leaf.path  # => root/branch/leaf
puts leaf.root.name  # => root

The visitor pattern combines with composites to add operations without modifying component classes. Components accept visitors and forward them to children. Visitors implement operations as visit methods for different component types. This separation allows adding new operations by creating new visitors rather than modifying the composite structure.

class VisitableComponent
  def accept(visitor)
    raise NotImplementedError
  end
end

class VisitableLeaf < VisitableComponent
  attr_reader :data
  
  def initialize(data)
    @data = data
  end
  
  def accept(visitor)
    visitor.visit_leaf(self)
  end
end

class VisitableComposite < VisitableComponent
  attr_reader :name, :children
  
  def initialize(name)
    @name = name
    @children = []
  end
  
  def add(component)
    @children << component
    self
  end
  
  def accept(visitor)
    visitor.visit_composite(self)
    @children.each { |child| child.accept(visitor) }
  end
end

class PrintVisitor
  def initialize
    @indent = 0
  end
  
  def visit_leaf(leaf)
    puts "#{'  ' * @indent}Leaf: #{leaf.data}"
  end
  
  def visit_composite(composite)
    puts "#{'  ' * @indent}Composite: #{composite.name}"
    @indent += 1
    yield if block_given?
    @indent -= 1
  end
end

class CountVisitor
  attr_reader :leaf_count, :composite_count
  
  def initialize
    @leaf_count = 0
    @composite_count = 0
  end
  
  def visit_leaf(leaf)
    @leaf_count += 1
  end
  
  def visit_composite(composite)
    @composite_count += 1
  end
end

# Building and visiting structure
root = VisitableComposite.new("root")
root.add(VisitableLeaf.new("A"))

branch = VisitableComposite.new("branch")
branch.add(VisitableLeaf.new("B"))
branch.add(VisitableLeaf.new("C"))
root.add(branch)

printer = PrintVisitor.new
root.accept(printer)

counter = CountVisitor.new
root.accept(counter)
puts "Leaves: #{counter.leaf_count}, Composites: #{counter.composite_count}"

The chain of responsibility pattern integrates with composites when requests should propagate up the tree until handled. Each node attempts to handle the request, and if unable, forwards it to its parent. This supports event bubbling in UI hierarchies or command processing in organizational structures.

class ChainComponent
  attr_accessor :parent
  
  def initialize(name)
    @name = name
    @parent = nil
  end
  
  def handle(request)
    if can_handle?(request)
      process(request)
    elsif @parent
      @parent.handle(request)
    else
      puts "#{request} not handled"
    end
  end
  
  def can_handle?(request)
    false
  end
  
  def process(request)
    puts "#{@name} handles #{request}"
  end
end

class SpecificHandler < ChainComponent
  def can_handle?(request)
    request.start_with?(@name)
  end
end

class ChainComposite < ChainComponent
  def initialize(name)
    super
    @children = []
  end
  
  def add(component)
    component.parent = self
    @children << component
    self
  end
end

# Building chain hierarchy
root = ChainComposite.new("root")
manager = SpecificHandler.new("manager")
developer = SpecificHandler.new("developer")

root.add(manager)
manager.parent = root

root.add(developer)
developer.parent = root

developer.handle("developer_task")  # => developer handles developer_task
developer.handle("manager_task")    # => manager handles manager_task
developer.handle("other_task")      # => other_task not handled

Common Pitfalls

The maximum uniformity pitfall occurs when placing all operations in the component interface, including those that apply only to composites. This forces leaf classes to implement methods like add or remove that make no sense for leaves. The resulting implementations either raise exceptions or silently fail, deferring type errors from compile time to runtime. This approach prioritizes uniform treatment over type safety.

# Pitfall: Leaf must implement composite operations
class ProblemLeaf
  def add(component)
    raise "Cannot add to leaf"  # Runtime error
  end
end

# Better: Use safe approach or document transparent tradeoff
class SafeLeaf
  # No add method - type error at call site
end

class TransparentLeaf
  def add(component)
    # Explicitly documented that this raises
    raise NotImplementedError, "Leaves cannot contain children"
  end
end

The parent reference inconsistency pitfall happens when composite modification methods fail to maintain parent references. Adding a component without setting its parent or removing without clearing the parent creates orphaned references. These inconsistencies cause incorrect results from operations that traverse parent chains.

# Pitfall: Forgetting to manage parent references
class BrokenComposite
  def add(component)
    @children << component
    # Missing: component.parent = self
  end
  
  def remove(component)
    @children.delete(component)
    # Missing: component.parent = nil
  end
end

# Better: Always manage parent references
class ProperComposite
  def add(component)
    remove_from_old_parent(component)
    component.parent = self
    @children << component
  end
  
  def remove(component)
    return unless @children.delete(component)
    component.parent = nil
  end
  
  private
  
  def remove_from_old_parent(component)
    component.parent.remove(component) if component.parent
  end
end

The shared component pitfall occurs when adding the same component to multiple composites. Most composite implementations assume exclusive ownership of children. Sharing a component between parents creates ambiguous parent references and leads to unexpected behavior during traversal or deletion. Cloning components before adding to multiple parents avoids this issue.

# Pitfall: Sharing component between parents
shared = Leaf.new("shared")
composite1.add(shared)
composite2.add(shared)  # Now shared.parent points to composite2 only

# Better: Clone before adding to second parent
shared = Leaf.new("shared")
composite1.add(shared)
composite2.add(shared.dup)

# Or: Explicitly support shared components with parent collection
class MultiParentComponent
  def initialize
    @parents = []
  end
  
  def add_parent(parent)
    @parents << parent unless @parents.include?(parent)
  end
  
  def remove_parent(parent)
    @parents.delete(parent)
  end
  
  def parents
    @parents.dup
  end
end

The deep hierarchy performance pitfall emerges when recursive operations traverse very deep trees repeatedly. Each operation that aggregates values from all nodes walks the entire subtree. For large or deep structures, this creates performance problems. Caching aggregate values and invalidating caches on modification improves performance significantly.

# Pitfall: Repeated deep traversal
class SlowComposite
  def size
    @children.sum(&:size)  # Recomputes every call
  end
end

# Better: Cache with invalidation
class FastComposite
  def initialize
    @children = []
    @size_cache = nil
  end
  
  def add(component)
    @children << component
    invalidate_size_cache
    component.on_change { invalidate_size_cache }
  end
  
  def size
    @size_cache ||= compute_size
  end
  
  private
  
  def compute_size
    @children.sum(&:size)
  end
  
  def invalidate_size_cache
    @size_cache = nil
    @parent&.invalidate_size_cache  # Propagate invalidation
  end
end

The circular reference pitfall happens when adding a composite to one of its descendants, creating a cycle in the tree. Traversal operations on cyclic structures loop infinitely. Preventing cycles requires checking during add operations whether the component being added contains the target composite in its subtree.

# Pitfall: Creating cycles
composite1.add(composite2)
composite2.add(composite1)  # Creates cycle

# Better: Check for cycles before adding
class SafeComposite
  def add(component)
    raise ArgumentError, "Cannot add ancestor" if contains?(component)
    @children << component
    component.parent = self
  end
  
  def contains?(component)
    return true if equal?(component)
    @children.any? do |child|
      child.respond_to?(:contains?) && child.contains?(component)
    end
  end
end

The memory leak pitfall occurs with parent references when components removed from trees retain their parent pointers. These orphaned references prevent garbage collection of the parent structure. Clearing parent references during removal ensures proper cleanup.

# Pitfall: Not clearing parent reference
def buggy_remove(component)
  @children.delete(component)
  # Parent reference remains, preventing GC
end

# Better: Clear parent on removal
def proper_remove(component)
  return false unless @children.delete(component)
  component.parent = nil
  true
end

Reference

Component Pattern Structure

Element Responsibility Implementation
Component Declares interface for objects in composition Module with abstract methods
Leaf Represents leaf objects with no children Includes Component, implements operations
Composite Defines behavior for components with children Includes Component, manages children
Client Manipulates objects through Component interface Uses Component methods only

Common Operations

Operation Purpose Leaf Behavior Composite Behavior
operation Perform primary domain action Execute directly Delegate to children, combine results
add Add child component Raise error or no-op Add to children collection
remove Remove child component Raise error or no-op Remove from children collection
get_child Access child by index Raise error or return nil Return child at index
parent Access parent component Return parent reference Return parent reference

Design Approaches

Approach Component Interface Type Safety Client Code Use When
Transparent All operations including add/remove Low - runtime errors Simple - uniform treatment Uniformity more important than safety
Safe Only common operations High - compile/duck-type errors Complex - type checking needed Type safety more important than uniformity

Traversal Strategies

Strategy Order Implementation Use Case
Pre-order Parent before children Process node, recurse on children Directory listings, expression prefix notation
Post-order Children before parent Recurse on children, process node Deletion, expression postfix notation
Level-order Level by level Use queue for breadth-first Minimum depth, level-wise processing
Depth-first Deepest first Use stack or recursion Path finding, exhaustive search

Performance Optimization Techniques

Technique Benefit Cost When to Use
Result caching Avoid repeated traversal Memory overhead, invalidation complexity Stable structures with frequent reads
Lazy evaluation Defer expensive computations Complexity in cache management Infrequently accessed aggregates
Parent references Enable upward traversal Memory per node, consistency management Need ancestor access or path computation
Iterative traversal Avoid recursion overhead More complex code Very deep trees, stack limit concerns

Common Method Signatures

# Component interface
module Component
  def operation; end
  def add(component); end
  def remove(component); end
  def get_child(index); end
  def parent; end
  def parent=(value); end
end

# Composite implementation
class Composite
  def initialize; end
  def add(component); end
  def remove(component); end
  def get_child(index); end
  def each(&block); end
  def operation; end
end

# Leaf implementation  
class Leaf
  def initialize(*args); end
  def operation; end
end

# Traversal methods
def traverse_preorder(&block); end
def traverse_postorder(&block); end
def traverse_levelorder(&block); end
def find_all(&predicate); end

Integration Patterns

Pattern Combination Benefit Implementation Notes
Iterator Standard traversal mechanism Include Enumerable, implement each
Visitor Separate operations from structure Accept visitor, forward to children
Chain of Responsibility Request propagation Handle or delegate to parent
Decorator Add responsibilities Wrap component, forward operations
Observer Change notification Notify observers on structural changes

Ruby-Specific Idioms

Idiom Purpose Example Usage
Module inclusion Define component interface include ComponentModule
Enumerable Iteration support include Enumerable; def each
Block traversal Flexible tree walking traverse { block }
Method chaining Fluent interface add(x).add(y).add(z)
Duck typing Flexible polymorphism respond_to?(:add)