Паттерн Компоновщик (Composite)

Название и классификация паттерна

Компоновщик — паттерн, структурирующий объекты.

Назначение

Компонует объекты в древовидные структуры для представления иерархий часть—целое. Позволяет клиентам единообразно трактовать индивидуальные и составные объекты.

Назначение паттерна Composite

Используйте паттерн Composite, если:

  • • необходимо объединять группы схожих объектов и управлять ими;
  • • объекты могут быть как примитивными (элементарными), так и составными (сложными). Составной объект может включать в себя коллекции других объектов, образуя сложные древовидные структуры. Пример: директория файловой системы состоит из элементов, каждый их которых также может быть директорией;
  • • код клиента работает с примитивными и составными объектами единообразно.

Описание паттерна Composite

Управление группами объектов может быть непростой задачей, особенно если эти объекты содержат собственные объекты. Паттерн Компоновщик описывает, как можно применить рекурсивную композицию таким образом, что клиенту не придется проводить различие между простыми и составными объектами (рис. 45).

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

Graphic

Draw ()

Add(Graphic)

Remove(Graphic)

GetChild(int)

Графические

DfThPiKThl

Picture

_

rtrOAl/ А _

Для всех графических ^

объектов д

дЦгал/()

LJI dW(j -Г~

Add(Graphic д) о-

Remove(Graphic)

GetChild(int)

  • 1
  • 1
  • 1

Добавить д в список ^ графических объектов

Рис. 45. Пример UML-диаграммы классов описания графического объекта

объекта (такие, как Draw) и общие для всех составных объектов, например операции для доступа и управления потомками.

Подклассы Line, Rectangle и Text (см. диаграмму выше) определяют примитивные графические объекты. В них операция Draw реализована соответственно для рисования прямых, прямоугольников и текста. Поскольку у примитивных объектов нет потомков, то ни один из этих подклассов не реализует операции, относящиеся к управлению потомками.

Класс Picture определяет агрегат, состоящий из объектов Graphic. Реализованная в нем операция Draw вызывает одноименную функцию для каждого потомка, а операции для работы с потомками уже не пусты. Поскольку интерфейс класса Picture соответствует интерфейсу Graphic, то в состав объекта Picture могут входить и другие такие же объекты.

Для добавления или удаления объектов-потомков в составной объект Composite класс Component определяет интерфейсы add() и remove().

Применимость

Используйте паттерн Компоновщик, когда:

  • • объекты должны быть представлены как иерархия вида часть-целое;
  • • необходимо, чтобы клиенты единообразно трактовали составные и индивидуальные объекты.

Структура

Структура паттерна Компоновщик приведена на рис. 46.

UML-диаграмма классов паттерна Composite

Рис. 46. UML-диаграмма классов паттерна Composite

Участники

Component (Graphic) — компонент: объявляет интерфейс для компонуемых объектов; предоставляет подходящую реализацию операций по умолчанию, общую для всех классов; объявляет интерфейс для доступа к потомкам и управления ими; определяет интерфейс для доступа к родителю компонента в рекурсивной структуре и при необходимости реализует его. Описанная возможность необязательна.

Primitive (Rectangle, Line, Text и т. п.) — примитив: представляет листовые узлы композиции и не имеет потомков; определяет поведение примитивных объектов в композиции.

Composite (Picture) — составной объект: определяет поведение компонентов, у которых есть потомки; хранит компоненты-потомки; реализует относящиеся к управлению потомками операции в интерфейсе клдсса Component.

Client — клиент: манипулирует объектами композиции через интерфейс Component.

Отношения

Клиенты используют интерфейс класса Component для взаимодействия с объектами в составной структуре. Если получателем запроса является листовый объект Leaf, то он и обрабатывает запрос. Когда же получателем является составной объект Composite, то обычно он перенаправляет запрос своим потомкам, возможно, выполняя некоторые дополнительные операции до или после перенаправления.

Результаты

Паттерн Компоновщик:

  • определяет иерархии классов, состоящие из примитивных и составных объектов. Из примитивных объектов можно составлять более сложные, которые, в свою очередь, участвуют в более сложных композициях, и т. д. Любой клиент, ожидающий примитивного объекта, может работать и с составным;
  • упрощает архитектуру клиента. Клиенты могут единообразно работать с индивидуальными и объектами и с составными структурами. Обычно клиенту неизвестно, взаимодействует ли он с листовым или составным объектом. Это упрощает код клиента, поскольку нет необходимости писать функции, ветвящиеся в зависимости от того, с объектом какого класса они работают;
  • облегчает добавление новых видов компонентов. Новые подклассы классов Composite или Leaf будут автоматически работать с уже существующими структурами и клиентским кодом. Изменять клиента при добавлении новых компонентов не нужно;
  • способствует созданию общего дизайна. Однако такая простота добавления новых компонентов имеет и свои отрицательные стороны: становится трудно наложить ограничения на то, какие объекты могут входить в состав композиции. Иногда желательно, чтобы составной объект мог включать только определенные виды компонентов.

Реализация паттерна Composite

Применим паттерн Composite для нашей стратегической игры. Сначала сформируем различные военные соединения римской армии, а затем рассчитаем разрушающую силу.

Каждая боевая единица (всадник, лучник, пехотинец) обладает своей собственной разрушающей силой. Эти единицы могут объединяться в группы для образования более сложных военных подразделений, например римские легионы, которые, в свою очередь, объединяясь, образуют армию. Как спроектировать такие иерархические соединения и рассчитать их боевые возможности?

Паттерн Composite вводит абстрактный базовый класс Component с поведением, общим для всех примитивных и составных объектов. Для случая стратегической игры — это метод getStrength() для подсчета разрушающей силы. Подклассы Primitive and Composite являются производными от класса Component. Составной объект Composite хранит компоненты-потомки абстрактного типа Component, каждый из которых может быть также Composite.

#include

#include

#include

// Component class Unit

{

public:

virtual int getStrength() = 0; virtual void addUnit(Unit* p) { assert( false);

}

virtual ~Unit() {}

};

// Primitives class Archer: public Unit {

public:

virtual int getStrength() { return 1;

}

};

class Infantryman: public Unit

{

public:

virtual int getStrength() { return 2;

}

};

class Horseman: public Unit

{

public:

virtual int getStrength() { return 3;

}

};

// Composite

class CompositeUnit: public Unit {

public:

int getStrengthO { int total = 0;

for(int i=0; igetStrength(); return total;

}

void addUnit(Unit* p) { c.push_back( p);

}

~CompositeUnit() { for(int i=0; i

}

private:

std::vector c;

};

// Вспомогательная функция для создания легиона Composite Unit* createLegion()

{

// Римский легион содержит:

CompositeUnit* legion = new Composite Unit;

// 3000 тяжелых пехотинцев for (int i=0; K3000; ++i) legion->addUnit(new Infantryman);

// 1200 легких пехотинцев for (int i=0; i< 1200; ++i) legion->addUnit(new Archer);

// 300 всадников for (int i=0; i<300; ++i) legion->addUnit(new Horseman);

return legion;

}

int main()

{

// Римская армия состоит из 4 легионов CompositeUnit* army = new CompositeUnit; for (int i=0; i<4; ++i) army->addUnit( createLegion());

cout << "Roman army damaging strength is"

<< army->getStrength() << endl;

II -

delete army; return 0;

Следует обратить внимание на один важный момент. Абстрактный базовый класс Unit объявляет интерфейс для добавления новых боевых единиц addUnit(), несмотря на то, что объектам примитивных типов (Archer, Infantryman, Horseman) подобная операция не нужна. Сделано это в угоду прозрачности системы в ущерб ее безопасности. Клиент знает, что объект типа Unit всегда будет иметь метод addUnit(). Однако его вызов для примитивных объектов считается ошибочным и небезопасным.

Можно сделать систему более безопасной, переместив метод addUnit() в составной объект CompositeUnit. Однако при этом возникает следующая проблема: мы не знаем, содержит ли объект Unit метод addUnit().

Рассмотрим следующий фрагмент кода.

class Unit

{

public:

virtual CompositeUnit* getComposite() { return 0;

}

П ...

};

// Composite

class CompositeUnit: public Unit

{

public:

void addUnit(Unit* p);

CompositeUnit* getComposite() { return this;

}

П ...

};

В абстрактном базовом классе Unit появился новый виртуальный метод getComposite() с реализацией по умолчанию, которая возвращает 0. Класс CompositeUnit переопределяет этот метод, возвращая указатель на самого себя. Благодаря этому методу можно запросить у компонента его тип. Если он составной, то можно применить операцию addUnit().

if (unit->getComposite())

{

unit->getComposite()->addUnit( new Archer);

Результаты применения паттерна Composite

Достоинства паттерна Composite

В систему легко добавлять новые примитивные или составные объекты, так как паттерн Composite использует общий базовый класс Component.

Код клиента имеет простую структуру — примитивные и составные объекты обрабатываются одинаковым образом.

Паттерн Composite позволяет легко обойти все узлы древовидной структуры.

Недостатки паттерна Composite

Неудобно осуществить запрет на добавление в составной объект Composite объектов определенных типов. Так, например, в состав римской армии не могут входить боевые слоны.

Родственные паттерны

Отношение компонент-родитель используется в паттерне Цепочка обязанностей.

Паттерн Декоратор часто применяется совместно с Компоновщиком. Когда Декораторы и Компоновщики используются вместе, у них обычно бывает общий родительский класс. Поэтому Декораторам придется поддержать интерфейс компонентов такими операциями, как Add, Remove и GetChild.

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

Итератор можно использовать для обхода составных объектов.

Посетитель локализует операции и поведение, которые в противном случае пришлось бы распределять между классами Composite и Leaf.

 
< Пред   СОДЕРЖАНИЕ     След >