Компоновщик (шаблон проектирования)
Компоновщик (англ. Composite pattern) — структурный шаблон проектирования, объединяющий объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково.
Цель
Паттерн определяет иерархию классов, которые одновременно могут состоять из примитивных и сложных объектов, упрощает архитектуру клиента, делает процесс добавления новых видов объекта более простым.
Описание
UML-диаграмма шаблона:
Примеры реализации
Пример на Java
Исходный текст на языке Java import java.util.List; import java.util.ArrayList; /** "Component" */ interface Graphic { //Prints the graphic. public void print(); } /** "Composite" */ class CompositeGraphic implements Graphic { //Collection of child graphics. private List<Graphic> mChildGraphics = new ArrayList<Graphic>(); //Prints the graphic. public void print() { for (Graphic graphic : mChildGraphics) { graphic.print(); } } //Adds the graphic to the composition. public void add(Graphic graphic) { mChildGraphics.add(graphic); } //Removes the graphic from the composition. public void remove(Graphic graphic) { mChildGraphics.remove(graphic); } } /** "Leaf" */ class Ellipse implements Graphic { //Prints the graphic. public void print() { System.out.println("Ellipse"); } } /** Client */ public class Program { public static void main(String[] args) { //Initialize four ellipses Ellipse ellipse1 = new Ellipse(); Ellipse ellipse2 = new Ellipse(); Ellipse ellipse3 = new Ellipse(); Ellipse ellipse4 = new Ellipse(); //Initialize three composite graphics CompositeGraphic graphic = new CompositeGraphic(); CompositeGraphic graphic1 = new CompositeGraphic(); CompositeGraphic graphic2 = new CompositeGraphic(); //Composes the graphics graphic1.add(ellipse1); graphic1.add(ellipse2); graphic1.add(ellipse3); graphic2.add(ellipse4); graphic.add(graphic1); graphic.add(graphic2); //Prints the complete graphic (four times the string "Ellipse"). graphic.print(); } }Пример на C#
Исходный текст на языке C# class MainApp { static void Main() { // Create a tree structure Composite root = new Composite("root"); root.Add(new Leaf("Leaf A")); root.Add(new Leaf("Leaf B")); Composite comp = new Composite("Composite X"); comp.Add(new Leaf("Leaf XA")); comp.Add(new Leaf("Leaf XB")); root.Add(comp); root.Add(new Leaf("Leaf C")); // Add and remove a leaf Leaf leaf = new Leaf("Leaf D"); root.Add(leaf); root.Remove(leaf); // Recursively display tree root.Display(1); // Wait for user Console.Read(); } } /// <summary> /// Component - компонент /// </summary> /// <li> /// <lu>объявляет интерфейс для компонуемых объектов;</lu> /// <lu>предоставляет подходящую реализацию операций по умолчанию, /// общую для всех классов;</lu> /// <lu>объявляет интерфейс для доступа к потомкам и управлению ими;</lu> /// <lu>определяет интерфейс доступа к родителю компонента в рекурсивной структуре /// и при необходимости реализует его. Описанная возможность необязательна;</lu> /// </li> abstract class Component { protected string name; // Constructor public Component(string name) { this.name = name; } public abstract void Display(int depth); } /// <summary> /// Composite - составной объект /// </summary> /// <li> /// <lu>определяет поведение компонентов, у которых есть потомки;</lu> /// <lu>хранит компоненты-потомоки;</lu> /// <lu>реализует относящиеся к управлению потомками операции и интерфейсе /// класса <see cref="Component"/></lu> /// </li> class Composite : Component { private List<Component> children = new List<Component>(); // Constructor public Composite(string name) : base(name) { } public void Add(Component component) { children.Add(component); } public void Remove(Component component) { children.Remove(component); } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + name); // Recursively display child nodes foreach (Component component in children) { component.Display(depth + 2); } } } /// <summary> /// Leaf - лист /// </summary> /// <remarks> /// <li> /// <lu>представляет листовой узел композиции и не имеет потомков;</lu> /// <lu>определяет поведение примитивных объектов в композиции;</lu> /// </li> /// </remarks> class Leaf : Component { // Constructor public Leaf(string name) : base(name) { } public override void Display(int depth) { Console.WriteLine(new String('-', depth) + name); } }Пример на C++
Исходный текст на языке C++ #include <iostream> #include <list> #include <algorithm> #include <memory> class IText{ public: typedef std::shared_ptr<IText> SPtr; virtual void draw() = 0; virtual void add(const SPtr&) { throw std::runtime_error("IText: Can't add to a leaf"); } virtual void remove(const SPtr&){ throw std::runtime_error("IText: Can't remove from a leaf"); } }; class CompositeText: public IText{ public: void add(const SPtr& sptr){ children_.push_back(sptr); } void remove(const SPtr& sptr){ children_.remove(sptr); } void replace(const SPtr& oldValue, const SPtr& newValue){ std::replace(children_.begin(), children_.end(), oldValue, newValue); } virtual void draw(){ for(SPtr& sptr : children_){ sptr->draw(); } } private: std::list<SPtr> children_; }; class Letter: public IText{ public: Letter(char c):c_(c) {} virtual void draw(){ std::cout<<c_; } private: char c_; }; int main(){ CompositeText sentence; IText::SPtr lSpace(new Letter(' ')); IText::SPtr lExcl(new Letter('!')); IText::SPtr lComma(new Letter(',')); IText::SPtr lNewLine(new Letter(' ')); IText::SPtr lH(new Letter('H')); // letter 'H' IText::SPtr le(new Letter('e')); // letter 'e' IText::SPtr ll(new Letter('l')); // letter 'l' IText::SPtr lo(new Letter('o')); // letter 'o' IText::SPtr lW(new Letter('W')); // letter 'W' IText::SPtr lr(new Letter('r')); // letter 'r' IText::SPtr ld(new Letter('d')); // letter 'd' IText::SPtr li(new Letter('i')); // letter 'i' IText::SPtr wHello(new CompositeText); wHello->add(lH); wHello->add(le); wHello->add(ll); wHello->add(ll); wHello->add(lo); IText::SPtr wWorld(new CompositeText); // word "World" wWorld->add(lW); wWorld->add(lo); wWorld->add(lr); wWorld->add(ll); wWorld->add(ld); sentence.add(wHello); sentence.add(lComma); sentence.add(lSpace); sentence.add(wWorld); sentence.add(lExcl); sentence.add(lNewLine); sentence.draw(); // prints "Hello, World! " IText::SPtr wHi(new CompositeText); // word "Hi" wHi->add(lH); wHi->add(li); sentence.replace(wHello, wHi); sentence.draw(); // prints "Hi, World! " sentence.remove(wWorld); sentence.remove(lSpace); sentence.remove(lComma); sentence.draw(); // prints "Hi! " return 0; }Пример на D
Исходный текст на языке D import std.stdio; abstract class TInfo { protected: string name; public: void Info(); } class TFile: TInfo { protected: uint size; public: this(const string theName, uint theSize) { name = theName; size = theSize; } void Info() { writefln("%s %d", name, size); } } class TDir: TInfo { protected: TInfo[] info; public: this(const string theName) { name = theName; } void Info() { writefln("[%s]", name); foreach (f; info) { f.Info(); } } void Add(TInfo theInfo) { info ~= theInfo; } } void main() { TDir first = new TDir("first"); first.Add(new TFile("a.txt", 100)); first.Add(new TFile("b.txt", 200)); first.Add(new TFile("c.txt", 300)); TDir second = new TDir("second"); second.Add(new TFile("d.txt", 400)); second.Add(new TFile("e.txt", 500)); TDir root = new TDir("root"); root.Add(first); root.Add(second); root.Info(); }Пример на Python
Исходный текст на языке Python from abc import ABCMeta, abstractmethod class Unit(metaclass=ABCMeta): """ Абстрактный компонент, в данном случае это - отряд (отряд может состоять из одного солдата или более) """ @abstractmethod def print(self) -> None: """ Вывод данных о компоненте """ pass class Archer(Unit): """ Лучник """ def print(self) -> None: print('лучник', end=' ') class Knight(Unit): """ Рыцарь """ def print(self) -> None: print('рыцарь', end=' ') class Swordsman(Unit): """ Мечник """ def print(self) -> None: print('мечник', end=' ') class Squad(Unit): """ Компоновщик - отряд, состоящий более чем из одного человека. Также может включать в себя другие отряды-компоновщики. """ def __init__(self): self._units = [] def print(self) -> None: print("Отряд {} (".format(self.__hash__()), end=' ') for u in self._units: u.print() print(')') def add(self, unit: Unit) -> None: """ Добавление нового отряда :param unit: отряд (может быть как базовым, так и компоновщиком) """ self._units.append(unit) unit.print() print('присоединился к отряду {}'.format(self.__hash__())) print() def remove(self, unit: Unit) -> None: """ Удаление отряда из текущего компоновщика :param unit: объект отряда """ for u in self._units: if u == unit: self._units.remove(u) u.print() print('покинул отряд {}'.format(self.__hash__())) print() break else: unit.print() print('в отряде {} не найден'.format(self.__hash__())) print() if __name__ == '__main__': print('OUTPUT:') squad = Squad() squad.add(Knight()) squad.add(Knight()) squad.add(Archer()) swordsman = Swordsman() squad.add(swordsman) squad.remove(swordsman) squad.print() squad_big = Squad() squad_big.add(Swordsman()) squad_big.add(Swordsman()) squad_big.add(squad) squad_big.print() ''' OUTPUT: рыцарь присоединился к отряду -9223363262492103834 рыцарь присоединился к отряду -9223363262492103834 лучник присоединился к отряду -9223363262492103834 мечник присоединился к отряду -9223363262492103834 мечник покинул отряд -9223363262492103834 Отряд -9223363262492103834 ( рыцарь рыцарь лучник ) мечник присоединился к отряду 8774362671992 мечник присоединился к отряду 8774362671992 Отряд -9223363262492103834 ( рыцарь рыцарь лучник ) присоединился к отряду 8774362671992 Отряд 8774362671992 ( мечник мечник Отряд -9223363262492103834 ( рыцарь рыцарь лучник ) ) '''Пример на PHP5
Исходный текст на языке PHP5 <?php abstract class Component { protected $name; public function __construct($name) { $this->name = $name; } public abstract function display(); } class Composite extends Component { private $children = array(); public function add(Component $component) { $this->children[$component->name] = $component; } public function remove(Component $component) { unset($this->children[$component->name]); } public function display() { foreach($this->children as $child) { $child->display(); } } } class Leaf extends Component { public function display() { print_r($this->name); } } // Create a tree structure $root = new Composite("root"); $root->add(new Leaf("Leaf A")); $root->add(new Leaf("Leaf B")); $comp = new Composite("Composite X"); $comp->add(new Leaf("Leaf XA")); $comp->add(new Leaf("Leaf XB")); $root->add($comp); $root->add(new Leaf("Leaf C")); // Add and remove a leaf $leaf = new Leaf("Leaf D"); $root->add($leaf); $root->remove($leaf); // Recursively display tree $root->display(); ?>Пример компоновщика с внешним итератором на PHP5
Исходный текст на языке PHP5 /** * Паттерн-компоновщик с внешним итератором * Итератор использует рекурсию для перебора дерева элементов */ namespace compositeIterator{ /** * Клиент использует интерфейс AComponent для работы с объектами. * Интерфейс AComponent определяет интерфейс для всех компонентов: как комбинаций, так и листовых узлов. * AComponent может реализовать поведение по умолчанию для add() remove() getChild() и других операций */ abstract class AComponent { public $customPropertyName; public $customPropertyDescription; /** * @param AComponent $component */ public function add($component) { throw new Exception("Unsupported operation"); } /** * @param AComponent $component */ public function remove($component) { throw new Exception("Unsupported operation"); } /** * @param int $int */ public function getChild($int) { throw new Exception("Unsupported operation"); } /** * @return IPhpLikeIterator */ abstract function createIterator(); public function operation1() { throw new Exception("Unsupported operation"); } } /** * Leaf наследует методы add() remove() getChild( которые могут не иметь смысла для листового узла. * Хотя листовой узел можно считать узлом с нулём дочерних объектов * * Leaf определяет поведение элементов комбинации. Для этого он реализует операции, поддерживаемые интерфейсом Composite. */ class Leaf extends AComponent { public function __construct($name, $description = '') { $this->customPropertyName = $name; $this->customPropertyDescription = $description; } public function createIterator() { return new NullIterator(); } public function operation1() { echo (" I'am leaf {$this->customPropertyName}, i don't want to do operation 1. {$this->customPropertyDescription}"); } } class NullIterator implements IPhpLikeIterator { public function valid() { return (false); } public function next() { return (false); } public function current() { return (null); } public function remove() { throw new CException('unsupported operation'); } } /** * Интерфейс Composite определяет поведение компонентов, имеющих дочерние компоненты, и обеспечивает хранение последних. * * Composite также реализует операции, относящиеся к Leaf. Некоторые из них не могут не иметь смысла для комбинаций; в таких случаях генерируется исключение. */ class Composite extends AComponent { private $_iterator = null; /** * @var ArrayObject AComponent[] $components для хранения потомков типа AComponent */ public $components = null; public function __construct($name, $description = '') { $this->customPropertyName = $name; $this->customPropertyDescription = $description; } /** * @param AComponent $component */ public function add($component) { if (is_null($this->components)) { $this->components = new ArrayObject; } $this->components->append($component); } public function remove($component) { foreach ($this->components as $i => $c) { if ($c === $component) { unset($this->components[$i]); } } } public function getChild($int) { return ($this->components[$int]); } public function operation1() { echo " $this->customPropertyName $this->customPropertyDescription"; echo " --------------------------------"; $iterator = $this->components->getIterator(); while ($iterator->valid()) { $component = $iterator->current(); $component->operation1(); $iterator->next(); } } /** * @return CompositeIterator */ public function createIterator() { if (is_null($this->_iterator)) { $this->_iterator = new CompositeIterator($this->components->getIterator()); } return ($this->_iterator); } } /** * Рекурсивный итератор компоновщика */ class CompositeIterator implements IPhpLikeIterator { public $stack = array(); /** * @param ArrayIterator $componentsIterator */ public function __construct($componentsIterator) { //$this->stack= new ArrayObject; $this->stack[] = $componentsIterator; } public function remove() { throw new CException('unsupported operation'); } public function valid() { if (empty($this->stack)) { return (false); } else { /** @var $componentsIterator ArrayIterator */ // берём первый элемент $componentsIterator = array_shift(array_values($this->stack)); if ($componentsIterator->valid()) { return (true); } else { array_shift($this->stack); return ($this->valid()); } } } public function next() { /** @var $componentsIterator ArrayIterator */ $componentsIterator = current($this->stack); $component = $componentsIterator->current(); if ($component instanceof Composite) { array_push($this->stack, $component->createIterator()); } $componentsIterator->next(); //return($component); } public function current() { if ($this->valid()) { /** @var $componentsIterator ArrayIterator */ // берём первый элемент $componentsIterator = array_shift(array_values($this->stack)); return ($componentsIterator->current()); } else { return (null); } } } /** * Интерфейс Iterator должен быть реализован всеми итераторами. * Данный интерфейс является частью интерфейса стандартного php итератора. * Конкретный Iterator отвечает за управление текущей позицией перебора в конкретной коллекции. */ interface IPhpLikeIterator { /** * @abstract * @return boolean есть ли текущий элемент */ public function valid(); /** * @abstract * @return mixed перевести курсор дальше */ public function next(); /** * @abstract * @return mixed получить текущий элемент */ public function current(); /** * удалить текущий элемент коллекции * @abstract * @return void */ public function remove(); } class Client { /** * @var AComponent */ public $topItem; public function __construct($topItem) { $this->topItem = $topItem; } public function printOperation1() { $this->topItem->operation1(); } public function printOperation2() { echo " "; $iterator = $this->topItem->createIterator(); while ($iterator->valid()) { /** @var $component AComponent */ $component = $iterator->current(); if (strstr($component->customPropertyName, 'leaf1')) { echo (" I'm Client, I found leaf {$component->customPropertyName}, I'll just leave it here (for my 'first-leafs' tea collection). {$component->customPropertyDescription}"); } $iterator->next(); } } } class Test { public static function go() { $a = new Composite("c1"); $b = new Composite("c2"); $c = new Composite("c3"); $topItem = new Composite("top item"); $topItem->add($a); $topItem->add($b); $topItem->add($c); $a->add(new Leaf("c1-leaf1")); $a->add(new Leaf("c1-leaf2")); $b->add(new Leaf("c2-leaf1")); $b->add(new Leaf("c2-leaf2")); $b->add(new Leaf("c2-leaf3")); $c->add(new Leaf("c3-leaf1")); $c->add(new Leaf("c3-leaf2")); $client = new Client($topItem); $client->printOperation1(); $client->printOperation2(); } } Test::go(); }Пример на PHP5.4
Исходный текст на языке PHP5.4 <?php interface IComponent { function display(); } trait TComponent { public $name; public function __construct($name) { $this->name = $name; } public function display() { print $this->name.'<br>'.PHP_EOL; } } trait TComposite { use TComponent{ TComponent::display as displaySelf; } protected $children = array(); public function add(IComponent $item) { $this->children[$item->name] = $item; } public function remove(IComponent $item) { unset($this->children[$item->name]); } public function display() { $this->displaySelf(); foreach ($this->children as $child) { $child->display(); } } } class Composite implements IComponent { use TComposite; } class Leaf implements IComponent { use TComponent; } $root = new Composite("root"); $root->add(new Leaf("Leaf A")); $root->add(new Leaf("Leaf B")); $comp = new Composite("Composite X"); $comp->add(new Leaf("Leaf XA")); $comp->add(new Leaf("Leaf XB")); $root->add($comp); $root->add(new Leaf("Leaf C")); $leaf = new Leaf("Leaf D"); $root->add($leaf); $root->remove($leaf); $root->display();Пример на CoffeeScript
Исходный текст на языке CoffeeScriptПример болванки простенького физического движка
# Component class PObject collide : (pObj) -> addChild : (pObj) -> rmChild : (index) -> getChild : (index) -> # Leaf class PShape extends PObject collide : (pObj) -> # ... # Composite class PCollection extends PObject constructor : -> @children = [] collide : (pObj) -> child.collide(pObj) for child in @children return @ addChild : (pObj) -> @children.push(pObj) if pObj instanceof PObject return @ rmChild : (index) -> @children.splice(index, 1) return @ getChild : (index) -> @children[index]