Компоновщик — это структурный паттерн, который позволяет создавать дерево объектов и работать с ним так же, как и с единичным объектом.
Компоновщик давно стал синонимом всех задач, связанных с построением дерева объектов. Все операции компоновщика основаны на рекурсии и «суммировании» результатов на ветвях дерева.
Сложность:
Популярность:
Применимость: Паттерн Компоновщик встречается в любых задачах, которые связаны с построением дерева. Самый простой пример — составные элементы GUI, которые тоже можно рассматривать как дерево.
Признаки применения паттерна: Если из объектов строится древовидная структура, и со всеми объектами дерева, как и с самим деревом работают через общий интерфейс.
Концептуальный пример
Этот пример показывает структуру паттерна Компоновщик , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.rb: Пример структуры паттерна
# Базовый класс Компонент объявляет общие операции как для простых, так и для
# сложных объектов структуры.
#
# @abstract
class Component
# @return [Component]
def parent
@parent
end
# При необходимости базовый Компонент может объявить интерфейс для установки и
# получения родителя компонента в древовидной структуре. Он также может
# предоставить некоторую реализацию по умолчанию для этих методов.
#
# @param [Component] parent
def parent=(parent)
@parent = parent
end
# В некоторых случаях целесообразно определить операции управления потомками
# прямо в базовом классе Компонент. Таким образом, вам не нужно будет
# предоставлять конкретные классы компонентов клиентскому коду, даже во время
# сборки дерева объектов. Недостаток такого подхода в том, что эти методы
# будут пустыми для компонентов уровня листа.
#
# @abstract
#
# @param [Component] component
def add(component)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# @abstract
#
# @param [Component] component
def remove(component)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# Вы можете предоставить метод, который позволит клиентскому коду понять,
# может ли компонент иметь вложенные объекты.
#
# @return [Boolean]
def composite?
false
end
# Базовый Компонент может сам реализовать некоторое поведение по умолчанию или
# поручить это конкретным классам, объявив метод, содержащий поведение
# абстрактным.
#
# @abstract
#
# @return [String]
def operation
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# Класс Лист представляет собой конечные объекты структуры. Лист не может иметь
# вложенных компонентов.
#
# Обычно объекты Листьев выполняют фактическую работу, тогда как объекты
# Контейнера лишь делегируют работу своим подкомпонентам.
class Leaf < Component
# return [String]
def operation
'Leaf'
end
end
# Класс Контейнер содержит сложные компоненты, которые могут иметь вложенные
# компоненты. Обычно объекты Контейнеры делегируют фактическую работу своим
# детям, а затем «суммируют» результат.
class Composite < Component
def initialize
@children = []
end
# Объект контейнера может как добавлять компоненты в свой список вложенных
# компонентов, так и удалять их, как простые, так и сложные.
# @param [Component] component
def add(component)
@children.append(component)
component.parent = self
end
# @param [Component] component
def remove(component)
@children.remove(component)
component.parent = nil
end
# @return [Boolean]
def composite?
true
end
# Контейнер выполняет свою основную логику особым образом. Он проходит
# рекурсивно через всех своих детей, собирая и суммируя их результаты.
# Поскольку потомки контейнера передают эти вызовы своим потомкам и так далее,
# в результате обходится всё дерево объектов.
#
# @return [String]
def operation
results = []
@children.each { |child| results.append(child.operation) }
"Branch(#{results.join('+')})"
end
end
# Клиентский код работает со всеми компонентами через базовый интерфейс.
#
# @param [Component] component
def client_code(component)
puts "RESULT: #{component.operation}"
end
# Благодаря тому, что операции управления потомками объявлены в базовом классе
# Компонента, клиентский код может работать как с простыми, так и со сложными
# компонентами, вне зависимости от их конкретных классов.
#
# @param [Component] component
# @param [Component] component2
def client_code2(component1, component2)
component1.add(component2) if component1.composite?
print "RESULT: #{component1.operation}"
end
# Таким образом, клиентский код может поддерживать простые компоненты-листья...
simple = Leaf.new
puts 'Client: I\'ve got a simple component:'
client_code(simple)
puts "\n"
# ...а также сложные контейнеры.
tree = Composite.new
branch1 = Composite.new
branch1.add(Leaf.new)
branch1.add(Leaf.new)
branch2 = Composite.new
branch2.add(Leaf.new)
tree.add(branch1)
tree.add(branch2)
puts 'Client: Now I\'ve got a composite tree:'
client_code(tree)
puts "\n"
puts 'Client: I don\'t need to check the components classes even when managing the tree:'
client_code2(tree, simple)
output.txt: Результат выполнения
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)