> For the complete documentation index, see [llms.txt](https://frogs-organization-2.gitbook.io/oop.-sbornik./llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://frogs-organization-2.gitbook.io/oop.-sbornik./21.-strukturnye-patterny./komponovshik-composite.md).

# Компоновщик (Composite)

## Компоновщик (Composite)

### Суть паттерна <a href="#intent" id="intent"></a>

**Компоновщик** — это структурный паттерн проектирования, который позволяет сгруппировать множество объектов в древовидную структуру, а затем работать с ней так, как будто это единичный объект.

<figure><img src="https://refactoringguru.cn/images/patterns/content/composite/composite.png?id=73bcf0d94db360b636cd745f710d19db" alt="Паттерн Компоновщик" width="640"><figcaption></figcaption></figure>

### &#x20;Проблема <a href="#problem" id="problem"></a>

Паттерн Компоновщик имеет смысл только тогда, когда основная модель вашей программы может быть структурирована в виде дерева.

Например, есть два объекта: `Продукт` и `Коробка`. `Коробка` может содержать несколько `Продуктов` и других `Коробок` поменьше. Те, в свою очередь, тоже содержат либо `Продукты`, либо `Коробки` и так далее.

Теперь предположим, ваши `Продукты` и `Коробки` могут быть частью заказов. Каждый заказ может содержать как простые `Продукты` без упаковки, так и составные `Коробки`. Ваша задача состоит в том, чтобы узнать цену всего заказа.

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/composite/problem-ru.png?id=d05b9da7e4776c853e783ecbacd09308" alt="Структура сложного заказа" width="370"><figcaption><p>Заказ может состоять из различных продуктов, упакованных в собственные коробки.</p></figcaption></figure>

Если решать задачу в лоб, то вам потребуется открыть все коробки заказа, перебрать все продукты и посчитать их суммарную стоимость. Но это слишком хлопотно, так как типы коробок и их содержимое могут быть вам неизвестны. Кроме того, наперёд неизвестно и количество уровней вложенности коробок, поэтому перебрать коробки простым циклом не выйдет.

### &#x20;Решение <a href="#solution" id="solution"></a>

Компоновщик предлагает рассматривать `Продукт` и `Коробку` через единый интерфейс с общим методом получения стоимости.

`Продукт` просто вернёт свою цену. `Коробка` спросит цену каждого предмета внутри себя и вернёт сумму результатов. Если одним из внутренних предметов окажется коробка поменьше, она тоже будет перебирать своё содержимое, и так далее, пока не будут посчитаны все составные части.

<figure><img src="https://refactoringguru.cn/images/patterns/content/composite/composite-comic-1-ru.png?id=f8f5303a2010179bdae1371615e295a4" alt="Решение с Компоновщиком" width="600"><figcaption><p>Компоновщик рекурсивно запускает действие по всем элементам дерева — от корня к листьям.</p></figcaption></figure>

Для вас, клиента, главное, что теперь не нужно ничего знать о структуре заказов. Вы вызываете метод получения цены, он возвращает цифру, а вы не тонете в горах картона и скотча.

### &#x20;Аналогия из жизни <a href="#analogy" id="analogy"></a>

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/composite/live-example.png?id=548a7cec45b493af66e8bfe524a137d3" alt="Пример армейской структуры" width="280"><figcaption><p>Пример армейской структуры.</p></figcaption></figure>

Армии большинства государств могут быть представлены в виде перевёрнутых деревьев. На нижнем уровне у вас есть солдаты, затем взводы, затем полки, а затем целые армии. Приказы отдаются сверху и спускаются вниз по структуре командования, пока не доходят до конкретного солдата.

### &#x20;Структура <a href="#structure" id="structure"></a>

<figure><img src="https://refactoringguru.cn/images/patterns/diagrams/composite/structure-ru.png?id=43f15362885e26953c04791230ab72df" alt="Структура классов паттерна Компоновщик" width="360"><figcaption></figcaption></figure>

1. **Компонент** определяет общий интерфейс для простых и составных компонентов дерева.
2. **Лист** — это простой компонент дерева, не имеющий ответвлений.

   Из-за того, что им некому больше передавать выполнение, классы листьев будут содержать большую часть полезного кода.
3. **Контейнер** (или *композит*) — это составной компонент дерева. Он содержит набор дочерних компонентов, но ничего не знает об их типах. Это могут быть как простые компоненты-листья, так и другие компоненты-контейнеры. Но это не является проблемой, если все дочерние компоненты следуют единому интерфейсу.

   Методы контейнера переадресуют основную работу своим дочерним компонентам, хотя и могут добавлять что-то своё к результату.
4. **Клиент** работает с деревом через общий интерфейс компонентов.

   Благодаря этому, клиенту не важно, что перед ним находится — простой или составной компонент дерева.

### Применимость <a href="#applicability" id="applicability"></a>

&#x20;Когда вам нужно представить древовидную структуру объектов.

&#x20;Паттерн Компоновщик предлагает хранить в составных объектах ссылки на другие простые или составные объекты. Те, в свою очередь, тоже могут хранить свои вложенные объекты и так далее. В итоге вы можете строить сложную древовидную структуру данных, используя всего две основные разновидности объектов.

&#x20;Когда клиенты должны единообразно трактовать простые и составные объекты.

&#x20;Благодаря тому, что простые и составные объекты реализуют общий интерфейс, клиенту безразлично, с каким именно объектом ему предстоит работать.

### &#x20;Шаги реализации <a href="#checklist" id="checklist"></a>

1. Убедитесь, что вашу бизнес-логику можно представить как древовидную структуру. Попытайтесь разбить её на простые компоненты и контейнеры. Помните, что контейнеры могут содержать как простые компоненты, так и другие вложенные контейнеры.
2. Создайте общий интерфейс компонентов, который объединит операции контейнеров и простых компонентов дерева. Интерфейс будет удачным, если вы сможете использовать его, чтобы взаимозаменять простые и составные компоненты без потери смысла.
3. Создайте класс компонентов-листьев, не имеющих дальнейших ответвлений. Имейте в виду, что программа может содержать несколько таких классов.
4. Создайте класс компонентов-контейнеров и добавьте в него массив для хранения ссылок на вложенные компоненты. Этот массив должен быть способен содержать как простые, так и составные компоненты, поэтому убедитесь, что он объявлен с типом интерфейса компонентов.

   Реализуйте в контейнере методы интерфейса компонентов, помня о том, что контейнеры должны делегировать основную работу своим дочерним компонентам.
5. Добавьте операции добавления и удаления дочерних компонентов в класс контейнеров.

   Имейте в виду, что методы добавления/удаления дочерних компонентов можно поместить и в интерфейс компонентов. Да, это нарушит *принцип разделения интерфейса*, так как реализации методов будут пустыми в компонентах-листьях. Но зато все компоненты дерева станут действительно одинаковыми для клиента.

### &#x20;Преимущества и недостатки <a href="#pros-cons" id="pros-cons"></a>

* &#x20;Упрощает архитектуру клиента при работе со сложным деревом компонентов.
* &#x20;Облегчает добавление новых видов компонентов.
* &#x20;Создаёт слишком общий дизайн классов.

## Пример

<pre class="language-cpp"><code class="lang-cpp"># include &#x3C;iostream>
# include &#x3C;initializer_list>
# include &#x3C;memory>
# include &#x3C;vector>

using namespace std;
class Component;

using PtrComponent = shared_ptr&#x3C;Component>;
using VectorComponent = vector&#x3C;PtrComponent>;

class Component
{
public:
        using value_type = Component;
        using size_type = size_t;
        using iterator = VectorComponent::const_iterator;
        using const_iterator = VectorComponent::const_iterator;

        virtual ~Component() = default;

        virtual void operation() = 0;

        virtual bool isComposite() const { return false; }
        virtual bool add(initializer_list&#x3C;PtrComponent> comp) { return false; }
        virtual bool remove(const iterator&#x26; it) { return false; }
        virtual iterator begin() const { return iterator(); }
        virtual iterator end() const { return iterator(); }
};

class Figure : public Component
{
public:
        virtual void operation() override { cout &#x3C;&#x3C; "Figure method;" &#x3C;&#x3C; endl; }
};

class Camera : public Component
{
public:
        virtual void operation() override { cout &#x3C;&#x3C; "Camera method;" &#x3C;&#x3C; endl; }
};

class Composite : public Component
{
private:
        VectorComponent vec;

public:
        Composite() = default;
<strong>        Composite(PtrComponent first, ...);
</strong>
        void operation() override;

        bool isComposite() const override { return true; }
        bool add(initializer_list&#x3C;PtrComponent> list) override;
        bool remove(const iterator&#x26; it) override { vec.erase(it); return true; }
        iterator begin() const override { return vec.begin(); }
        iterator end() const override { return vec.end(); }
};

# pragma region Methods
Composite::Composite(PtrComponent first, ...)
{
        for (shared_ptr&#x3C;Component>* ptr = &#x26;first; *ptr; ++ptr)
                vec.push_back(*ptr);
}

void Composite::operation()
{
<strong>        cout &#x3C;&#x3C; "Composite method:" &#x3C;&#x3C; endl;
</strong>        for (auto elem : vec)
                elem->operation();
}

bool Composite::add(initializer_list&#x3C;PtrComponent> list)
{
        for(auto elem : list)
                vec.push_back(elem);
                
        return true;
}

# pragma endregion

int main()
{
        using Default = shared_ptr&#x3C;Component>;
        PtrComponent fig = make_shared&#x3C;Figure>(), cam = make_shared&#x3C;Camera>();
        auto composite1 = make_shared&#x3C;Composite>(fig, cam, Default{});
        
        composite1->add({ make_shared&#x3C;Figure>(), make_shared&#x3C;Camera>() });
        composite1->operation();
        cout &#x3C;&#x3C; endl;
        
        auto it = composite1->begin();
        
        composite1->remove(++it);
        composite1->operation();
        cout &#x3C;&#x3C; endl;
        
        auto composite2 = make_shared&#x3C;Composite>(make_shared&#x3C;Figure>(), composite1, Default());
        
        composite2->operation();
}

</code></pre>
