ЛЕКЦИЯ 10
Тема: Типовые решения для объекто-ориентированных программ
Паттерны (patterns) проектирования.
Литература:
Паттерны проектирования – это обобщенные решения проблем программного проектирования.
Объектно-ориентированные паттерны проектирования обычно показывают отношения и
взаимодействия между классами и объектами без специфики классов или объектов окончательного
приложения.
Паттерн проектирования – это описание задачи, которая постоянно возникает в ходе
проектирования объектно-ориентированных программ, и принципов ее решения. Причем данное решение
может быть использовано повторно. Смысл паттерна – предложить решение задачи в определенных
условиях.
В сравнении с полностью самостоятельным проектированием, паттерны обладают рядом
преимуществ. Основная польза от использования паттернов состоит в снижении сложности разработки за
счёт готовых абстракций для решения целого класса проблем. Паттерн даёт решению свое имя, что
облегчает коммуникацию между разработчиками, позволяя ссылаться на известные паттерны. Таким
образом, за счёт паттернов производится унификация деталей решений: модулей, элементов проекта, —
снижается количество ошибок. Применение паттернов концептуально сродни использованию готовых
библиотек кода. Правильно сформулированный паттерн проектирования позволяет, отыскав удачное
решение, пользоваться им снова и снова. Набор паттернов помогает разработчику выбрать возможный,
наиболее подходящий вариант проектирования
В литературе по ИТ-паттернам паттерн описывается как нечто, состоящее из четырех основных
элементов:
•
•
•
•
Имя. Сославшись на имя, можно сразу описать проблему, ее решения и последствия.
Присваивание паттернам имен позволяет проектировать на более высоком уровне абстракции. С
помощью словаря паттернов можно вести обсуждение с коллегами, упоминать паттерны в
документации, представлять тонкости системы.
Задача. Описание того, когда следует применять паттерн. Формулируется задача и ее контекст.
(Примером конкретной задачи может служить способ представления алгоритмов в виде объектов).
Иногда в описание задачи входит перечень условий, при выполнении которых имеет смысл
применять паттерн.
Решение. Описание элементов решения (элементов проектирования, анализа, тестирования и др. в зависимости от вида паттерна), отношений между ними, функций каждого элемента. При этом
решение - не конкретный дизайн или реализация, так как паттерн применяется в самых разных
контекстах. Просто дается абстрактное описание задачи и того, как она может быть решена с
помощью некоего весьма общего сочетания элементов (в случае проектирования, например, это
могут быть объекты и классы).
Результаты. Результаты - это следствия применения паттерна и разного рода компромиссы. Хотя
при описании решений о последствиях часто не упоминают, знать о них необходимо, чтобы
можно было выбирать между различными вариантами и оценивать преимущества и недостатки
конкретного паттерна. Иногда в результатах может быть описан выбор языка и реализации. В
случае проектирования к результатам относят влияние на степень гибкости, расширяемости и
переносимости системы. Перечисление всех последствий помогает понять и оценить их роль.
Паттерн проектирования — это описание взаимодействия объектов и классов , адаптированных
для решения общей задачи проектирования в конкретном контексте.
1
Паттерн проектирования именует, абстрагирует и идентифицирует ключевые аспекты структуры общего
решения, которые и позволяют применить его для создания повторно используемого дизайна. Он
вычленяет участвующие классы и экземпляры, их роли и отношения, а также функции.
Прото-паттерны
Не всякое решение, алгоритм, лучшие практики или эвристики могут быть названы паттерном.
Один или несколько признаков паттерна могут отсутствовать. Даже если все признаки паттерна есть, но
описанная проблема и/или решение не являются повторяющимися, предлагаемую идею нельзя назвать
паттерном. Существует мнение, что такой находящийся в разработке паттерн (прото-паттерн) должен
быть применен по крайней мере трижды, просмотрен и одобрен значительным числом пользователей
паттернов. Краткое описание прото-паттерна называется patlet.
Анти-паттерны
В то время как обычный паттерн описывает положительный опыт решения задачи, анти-паттерны
описывают отрицательный опыт. Антипаттерны появились в середине 1990-х и не так многочисленны, как
обычные паттерны. Под анти-паттернами понимают:
1. паттерны, описывающие плохое решение задачи, которое привело к печальным последствиям;
2. паттерны, описывающие выход из этого печального состояния и переход к хорошему решению.
Мы кратко рассмотрим следующие типы паттернов проектирования:
Порождающие паттерны
Структурные паттерны
Паттерны поведения
Паттерны проектирования
№
Название
паттерна
Перевод
Назначение паттерна
Порождающие паттерны
1
Factory Method
Фабричный метод
2
Abstract Factory
Абстрактная
фабрика
3
Singleton
Одиночка
4
Prototype
Прототип
5
Builder
Строитель
Определяет интерфейс для разработки объектов, при этом
объекты данного класса могут быть созданы его подклассами.
Предоставляет интерфейс для создания множества связанных
между собой или независимых объектов, конкретные классы
которых неизвестны.
Для выбранного класса обеспечивает выполнение требования
единственности экземпляра и предоставления к нему полного
доступа.
Описывает виды создаваемых объектов с помощью прототипа,
что позволяет создавать новые объекты путем копирования
этого прототипа.
Отделяет создание сложного объекта от его представления,
позволяя использовать один и тот же процесс разработки для
создания различных представлений.
Структурные паттерны
1
Adapter(синоним
Адаптер (Обертка)
- Wrapper)
2
Decorator
Декоратор
Преобразует существующий интерфейс класса в другой
интерфейс, который понятен клиентам. При этом обеспечивает
совместную работу классов, невозможную без данного
паттерна из-за несовместимости интерфейсов.
Применяется для расширения имеющейся функциональности
и является альтернативой порождению подклассов на основе
2
3
Proxy
Заместитель
4
Composite
Компоновщик
5
Bridge
Мост
6
Flyweight
Приспособленец
7
Facade
Фасад
динамического назначения объектам новых операций.
Подменяет выбранный объект другим объектом для
управления контроля доступа к исходному объекту.
Группирует объекты в иерархические структуры для
представления отношений типа "часть-целое", что позволяет
клиентам работать с единичными объектами так же, как с
группами объектов.
Отделяет абстракцию класса от его реализации, благодаря
чему появляется возможность независимо изменять то и
другое.
Использует принцип разделения для эффективной поддержки
большого числа мелких объектов.
Предоставляет единый интерфейс к множеству операций или
интерфейсов в системе на основе унифицированного
интерфейса для облегчения работы с системой.
Паттерны поведения
of Цепочка
обязанностей
1
Chain
Responsibility
2
Command
Команда
3
Interpreter
Интерпретатор
4
Iterator
Итератор
5
Mediator
Посредник
6
Memento
Хранитель
7
Observer
Наблюдатель
8
State
Состояние
9
Strategy
Стратегия
10 Template Method Шаблонный метод
11 Visitor
Посетитель
Позволяет избежать жесткой зависимости отправителя запроса
от его получателя, при этом объекты-получатели связываются
в цепочку, а запрос передается по цепочке, пока какой-то
объект его не обработает.
Инкапсулирует запрос в виде объекта, обеспечивая
параметризацию клиентов типом запроса, установление
очередности запросов, протоколирование запросов и отмену
выполнения операций.
Для заданного языка определяет представление его
грамматики на основе интерпретатора предложений языка,
использующего это представление.
Дает возможность последовательно перебрать все элементы
составного объекта, не раскрывая его внутреннего
представления.
Определяет объект, в котором инкапсулировано знание о том,
как взаимодействуют объекты из некоторого множества.
Способствует уменьшению числа связей между объектами,
позволяя им работать без явных ссылок друг на друга и
независимо изменять схему взаимодействия.
Дает возможность получить и сохранить во внешней памяти
внутреннее состояние объекта, чтобы позже объект можно
было восстановить точно в таком же состоянии, не нарушая
принципа инкапсуляции.
Специфицирует зависимость типа "один ко многим" между
различными объектами, так что при изменении состояния
одного объекта все зависящие от него получают извещение и
автоматически обновляются.
Позволяет выбранному объекту варьировать свое поведение
при изменении внутреннего состояния. При этом создается
впечатление, что изменился класс объекта.
Определяет множество алгоритмов, инкапсулируя их все и
позволяя подставлять один вместо другого. При этом можно
изменять алгоритм независимо от клиента, который им
пользуется.
Определяет
структуру
алгоритма,
перераспределяя
ответственность за некоторые его шаги на подклассы. При
этом подклассы могут переопределять шаги алгоритма, не
меняя его общей структуры.
Позволяет определить новую операцию, не меняя описаний
классов, у объектов которых она вызывается.
Порождающие паттерны
3
Порождающие паттерны проектирования предназначены для создания объектов,
позволяя системе оставаться независимой как от самого процесса порождения, так и от типов
порождаемых объектов.
Паттерн Factory Method (фабричный метод)
Назначение паттерна Factory Method
В системе часто требуется создавать объекты самых разных типов. Паттерн Factory Method (фабричный
метод) может быть полезным в решении следующих задач:
•
•
Система должна оставаться расширяемой путем добавления объектов новых типов.
Непосредственное использование выражения new является нежелательным, так как в этом случае
код создания объектов с указанием конкретных типов может получиться разбросанным по всему
приложению. Тогда такие операции как добавление в систему объектов новых типов или замена
объектов одного типа на другой будут затруднительными. Паттерн Factory Method позволяет
системе оставаться независимой как от самого процесса порождения объектов, так и от их типов.
Заранее известно, когда нужно создавать объект, но неизвестен его тип.
Описание паттерна Factory Method
Для того, чтобы система оставалась независимой от различных типов объектов, паттерн Factory
Method использует механизм полиморфизма - классы всех конечных типов наследуют от одного
абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе
4
определяется единый интерфейс, через который пользователь будет оперировать объектами конечных
типов.
Для обеспечения относительно простого добавления в систему новых типов паттерн Factory
Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого
класса, посредством которых создаются объекты конкретных классов, называются фабричными.
Существуют две разновидности паттерна Factory Method:
Обобщенный конструктор, когда в том же самом полиморфном базовом классе, от которого
наследуют производные классы всех создаваемых в системе типов, определяется статический фабричный
метод. В качестве параметра в этот метод должен передаваться идентификатор типа создаваемого объекта.
UML-диаграмма классов паттерна Factory Method. Обобщенный конструктор
Классический вариант фабричного метода, когда интерфейс фабричных методов объявляется в
независимом классе-фабрике, а их реализация определяется конкретными подклассами этого класса.
UML-диаграмма классов паттерна Factory Method. Классическая реализация
#include
#include
#include
#include
"pch.h"
using namespace std;
enum Car_ID { Vaz_ID = 0, Uaz_ID, Truck_ID };
5
class Car
{
public:
virtual void info() = 0;
virtual ~Car() {}
// Параметризированный статический фабричный метод
static Car * createCar(Car_ID id);
};
class Vaz : public Car
{
public:
void info() { cout << "ВАЗ 2115" << endl; }
};
class Uaz : public Car
{
public:
void info() { cout << "УАЗ-3163 Патриот" << endl; }
};
class Truck : public Car
{
public:
void info() { cout << "Грузовик" << endl; }
};
// Реализация параметризированного фабричного метода
Car * Car::createCar(Car_ID id)
{
Car * ptrCar=nullptr;
switch (id)
{
case Vaz_ID:
ptrCar = new Vaz();
break;
case Uaz_ID:
ptrCar = new Uaz();
break;
case Truck_ID:
ptrCar = new Truck();
break;
default:
assert(false);
}
return ptrCar;
};
// Создание объектов при помощи параметризированного фабричного метода
int main()
{
setlocale(LC_ALL, "rus");
vector v;
v.push_back(Car::createCar(Vaz_ID));
v.push_back(Car::createCar(Uaz_ID));
v.push_back(Car::createCar(Truck_ID));
//
for (int i = 0; iinfo();
}
6
Представленный вариант паттерна Factory Method пользуется популярностью благодаря своей простоте. В
нем статический фабричный метод createCar() определен непосредственно в полиморфном базовом классе
Car. Этот фабричный метод является параметризированным, то есть для создания объекта некоторого
типа в createCar() передается соответствующий идентификатор типа.
С точки зрения "чистоты" объектно-ориентированного кода у этого варианта есть следующие недостатки:
•
•
Так как код по созданию объектов всех возможных типов сосредоточен в статическом фабричном
методе класса Car, то этот базовый класс обладает знанием обо всех производных от него классах,
что является нетипичным для объектно-ориентированного подхода.
Подобное использование оператора switch (как в коде фабричного метода createCar()) в объектноориентированном программировании также не приветствуется.
Указанные недостатки отсутствуют в классической реализации паттерна Factory Method.
#include "pch.h"
// ConsoleApplication53.cpp : Классическая реализация паттерна Factory Method
//
#include
#include
using namespace std;
// Иерархия классов игровых персонажей
class Car
{
public:
virtual void info() = 0;
virtual ~Car() {}
};
class Vaz : public Car
{
public:
void info() { cout << "ВАЗ 2115" << endl; }
};
class Uaz : public Car
{
public:
void info() { cout << "УАЗ-3163 Патриот" << endl; }
};
class Truck : public Car
{
public:
void info() { cout << "Грузовик" << endl; }
};
// Фабрики объектов
class Factory
{
public:
virtual Car * createCar() = 0;
virtual ~Factory() {}
};
7
class VazFactory : public Factory
{
public:
Car* createCar() {
return new Vaz;
}
};
class UazFactory : public Factory
{
public:
Car * createCar() {
return new Uaz;
}
};
class TruckFactory : public Factory
{
public:
Car* createCar() {
return new Truck;
}
};
// Создание объектов при помощи фабрик объектов
int main()
{
setlocale(LC_ALL, "rus");
VazFactory *vaz_factory = new VazFactory;
UazFactory* uaz_factory = new UazFactory;
TruckFactory* truck_factory = new TruckFactory;
vector v;
v.push_back(vaz_factory->createCar());
v.push_back(uaz_factory->createCar());
v.push_back(uaz_factory->createCar());
//foreach (Car * p in v)
for(Car *p:v)
{
p->info();
delete p;
}
}
Классический вариант паттерна Factory Method использует идею полиморфной фабрики.
Специально выделенный для создания объектов полиморфный базовый класс Factory объявляет
интерфейс фабричного метода createCar(), а производные классы его реализуют.
Представленный вариант паттерна Factory Method является наиболее распространенным, но не
единственным. Возможны следующие вариации:
•
•
Класс Factory имеет реализацию фабричного метода createCar() по умолчанию.
Фабричный метод createCar()класса Factory параметризирован типом создаваемого объекта (как и
у представленного ранее, простого варианта Factory Method) и имеет реализацию по умолчанию. В
8
этом случае, производные от Factory классы необходимы лишь для того, чтобы определить
нестандартное поведение createCar().
Результаты применения паттерна Factory Method
Достоинства паттерна Factory Method
•
Создает объекты разных типов, позволяя системе оставаться независимой как от самого процесса
создания, так и от типов создаваемых объектов.
Недостатки паттерна Factory Method
•
В случае классического варианта паттерна даже для порождения единственного объекта
необходимо создавать соответствующую фабрику.
Паттерн Abstract Factory (абстрактная фабрика)
Использует несколько фабричных методов и предназначен для создания целого семейства или группы
взаимосвязанных объектов.
Назначение паттерна Abstract Factory
Используйте паттерн Abstract Factory (абстрактная фабрика) если:
•
•
Система должна оставаться независимой как от процесса создания новых объектов, так и от типов
порождаемых объектов. Непосредственное использование выражения new в коде приложения
нежелательно.
Необходимо создавать группы или семейства взаимосвязанных объектов, исключая возможность
одновременного использования объектов из разных семейств в одном контексте.
Приведем примеры групп взаимосвязанных объектов.
Пусть некоторое приложение с поддержкой графического интерфейса пользователя рассчитано на
использование на различных платформах, при этом внешний вид этого интерфейса должен
соответствовать принятому стилю для той или иной платформы. Например, если это приложение
установлено на Windows-платформу, то его кнопки, меню, полосы прокрутки должны отображаться в
стиле, принятом для Windows. Группой взаимосвязанных объектов в этом случае будут элементы
графического интерфейса пользователя для конкретной платформы.
Другой пример. Рассмотрим текстовый редактор с многоязычной поддержкой, у которого имеются
функциональные модули, отвечающие за расстановку переносов слов и проверку орфографии. Если,
скажем, открыт документ на русском языке, то должны быть подключены соответствующие модули,
учитывающие специфику русского языка. Ситуация, когда для такого документа одновременно
используются модуль расстановки переносов для русского языка и модуль проверки орфографии для
немецкого языка, исключается. Здесь группой взаимосвязанных объектов будут соответствующие модули,
учитывающие специфику некоторого языка.
Описание паттерна Abstract Factory
Паттерн Abstract Factory реализуется на основе фабричных методов (см. паттерн Factory Method).
Любое семейство или группа взаимосвязанных объектов характеризуется несколькими общими
типами создаваемых продуктов, при этом сами продукты таких типов будут различными для разных
9
семейств. Для того чтобы система оставалась независимой от специфики того или иного семейства
продуктов необходимо использовать общие интерфейсы для всех основных типов продуктов.
Для решения задачи по созданию семейств взаимосвязанных объектов паттерн Abstract Factory
вводит понятие абстрактной фабрики. Абстрактная фабрика представляет собой некоторый полиморфный
базовый класс, назначением которого является объявление интерфейсов фабричных методов, служащих
для создания продуктов всех основных типов (один фабричный метод на каждый тип продукта).
Производные от него классы, реализующие эти интерфейсы, предназначены для создания продуктов всех
типов внутри семейства или группы.
#include "pch.h"
// ConsoleApplication54.cpp : Реализация паттерна Abstract Factory
//
//#include "stdafx.h"
#include
#include
using namespace std;
// Абстрактные базовые классы всех возможных видов машин
class Vaz
{
public:
virtual void info() = 0;
virtual ~Vaz() {}
};
class Uaz
{
public:
virtual void info() = 0;
virtual ~Uaz() {}
};
10
class Truck
{
public:
virtual void info() = 0;
virtual ~Truck() {}
};
// Абстрактная фабрика для производства машин
class CarFactory
{
public:
virtual Vaz* createVaz() = 0;
virtual Uaz* createUaz() = 0;
virtual Truck* createTruck() = 0;
virtual ~CarFactory() {}
};
// Классы всех видов машин таксопарка
class TaxiVaz : public Vaz
{
public:
void info() { cout << "Ваз - такси" << endl; }
};
class TaxiUaz : public Uaz
{
public:
void info() { cout << "УАЗ - такси" << endl; }
};
class TaxiTruck : public Truck
{
public:
void info() { cout << "Грузовик - такси" << endl; }
};
// Классы всех видов машин автоколонны
class ParkVaz : public Vaz
{
public:
void info() { cout << "Ваз - автоколонна" << endl; }
};
class ParkUaz : public Uaz
{
public:
void info() { cout << "Уаз - автоколонна" << endl; }
};
class ParkTruck : public Truck
{
public:
void info() { cout << "Грузовик - автоколонна" << endl; }
};
// Фабрика для создания машин таксопарка
class TaxiFactory : public CarFactory
{
public:
Vaz* createVaz() { return new TaxiVaz; }
Uaz* createUaz() { return new TaxiUaz; }
Truck* createTruck() { return new TaxiTruck; }
};
11
// Фабрика для создания машин автоколонны
class ParkFactory : public CarFactory
{
public:
Vaz* createVaz() { return new ParkVaz; }
Uaz* createUaz() { return new ParkUaz; }
Truck* createTruck() { return new ParkTruck; }
};
// Класс, содержащий все машины предприятия
class Company
{
public:
~Company() {
for (Vaz *p : vv) delete p;
for (Uaz *p : vu) delete p;
for (Truck *p : vt) delete p;
}
void info() {
for (Vaz *p : vv) p->info();
for (Uaz *p : vu) p->info();
for (Truck *p : vt) p->info();
}
vector vv;
vector vu;
vector vt;
};
// Здесь создаются предприятия
class Holding
{
public:
Company* createCompany(CarFactory& factory, int vs, int us, int ts = 0) {
int i;
Company* p = new Company;
for (i = 0; i < vs; i++) p->vv.push_back(factory.createVaz());
for (i = 0; i < us; i++) p->vu.push_back(factory.createUaz());
for (i = 0; i < ts; i++) p->vt.push_back(factory.createTruck());
return p;
}
};
int main()
{
Holding hold;
TaxiFactory t_factory;
ParkFactory p_factory;
setlocale(LC_ALL, "rus");
Company * tx = hold.createCompany(t_factory, 3, 1, 0);
Company * pk = hold.createCompany(p_factory, 0, 2, 3);
cout << "Таксопарк:" << endl;
tx->info();
cout << endl << "автоколонна:" << endl;
pk->info();
}
12
Результаты применения паттерна Abstract Factory
Достоинства паттерна Abstract Factory
•
•
Скрывает сам процесс порождения объектов, а также делает систему независимой от типов
создаваемых объектов, специфичных для различных семейств или групп (пользователи оперируют
этими объектами через соответствующие абстрактные интерфейсы).
Позволяет быстро настраивать систему на нужное семейство создаваемых объектов. В случае
многоплатформенного графического приложения для перехода на новую платформу, то есть для
замены графических элементов (кнопок, меню, полос прокрутки) одного стиля другим достаточно
создать нужный подкласс абстрактной фабрики. При этом условие невозможности
одновременного использования элементов разных стилей для некоторой платформы будет
выполнено автоматически.
Недостатки паттерна Abstract Factory
•
Трудно добавлять новые типы создаваемых продуктов или заменять существующие, так как
интерфейс базового класса абстрактной фабрики фиксирован. Например, если для нашей
стратегической игры нужно будет ввести новый вид военной единицы - осадные орудия, то надо
будет добавить новый фабричный метод, объявив его интерфейс в полиморфном базовом классе
AbstractFactory и реализовав во всех подклассах. Снять это ограничение можно следующим
образом. Все создаваемые объекты должны наследовать от общего абстрактного базового класса, а
в единственный фабричный метод в качестве параметра необходимо передавать идентификатор
типа объекта, который нужно создать. Однако в этом случае необходимо учитывать следующий
момент. Фабричный метод создает объект запрошенного подкласса, но при этом возвращает его с
интерфейсом общего абстрактного класса в виде ссылки или указателя, поэтому для такого
объекта будет затруднительно выполнить какую-либо операцию, специфичную для подкласса.
13
Паттерн Builder (строитель)
Определяет процесс поэтапного конструирования сложного объекта, в результате
которого могут получаться разные представления этого объекта.
Назначение паттерна Builder
Паттерн Builder может помочь в решении следующих задач:
•
•
В системе могут существовать сложные объекты, создание которых за одну операцию
затруднительно или невозможно. Требуется поэтапное построение объектов с контролем
результатов выполнения каждого этапа.
Данные должны иметь несколько представлений. Приведем классический пример. Пусть есть
некоторый исходный документ в формате RTF (Rich Text Format), в общем случае содержащий
текст, графические изображения и служебную информацию о форматировании (размер и тип
шрифтов, отступы и др.). Если этот документ в формате RTF преобразовать в другие форматы
(например, Microsoft Word или простой ASCII-текст), то полученные документы и будут
представлениями исходных данных.
Описание паттерна Builder
Паттерн Builder отделяет алгоритм поэтапного конструирования сложного продукта (объекта) от
его внешнего представления так, что с помощью одного и того же алгоритма можно получать разные
представления этого продукта.
Поэтапное создание продукта означает его построение по частям. После того как построена
последняя часть, продукт можно использовать.
Для этого паттерн Builder определяет алгоритм поэтапного создания продукта в специальном
классе Director (распорядитель), а ответственность за координацию процесса сборки отдельных частей
продукта возлагает на иерархию классов Builder. В этой иерархии базовый класс Builder объявляет
интерфейсы для построения отдельных частей продукта, а соответствующие подклассы ConcreteBuilder их
реализуют подходящим образом, например, создают или получают нужные ресурсы, сохраняют
промежуточные результаты, контролируют результаты выполнения операций.
Класс Director содержит указатель или ссылку на Builder, который перед началом работы
должен
быть
сконфигурирован
экземпляром
ConcreteBuilder,
определяющим
соответствующее представление. После этого Director может обрабатывать клиентские
запросы на создание объекта. Получив такой запрос, с помощью имеющегося экземпляра
строителя Director строит продукт по частям, а затем возвращает его пользователю.
UML-диаграмма последовательности паттерна Builder
14
Для получения разных представлений некоторых данных с помощью паттерна Builder
распорядитель Director должен использовать соответствующие экземпляры ConcreteBuilder.
15
#include "pch.h"
// ConsoleApplication55.cpp : Defines the entry point for the console application.
//
#include
#include
using namespace std;
// Классы всех возможных машин
class Vaz
{
public:
void info() { cout << "ВАЗ 2115" << endl; }
};
16
class Uaz
{
public:
void info() { cout << "УАЗ-3163 Патриот" << endl; }
};
class Limo
{
public:
void info() { cout << "Лимузин" << endl; }
};
class Truck
{
public:
void info() { cout << "Грузовик" << endl; }
};
class Tractor
{
public:
void info() { cout << "Трактор" << endl; }
};
// Класс "Предприятие", содержащий все типы машин
class Company
{
public:
vector
vv;
vector
vu;
vector
vl;
vector
vt;
vector vtr;
void info() {
for (Vaz v : vv) v.info();
for (Uaz v : vu) v.info();
for (Limo v : vl) v.info();
for (Truck v : vt) v.info();
for (Tractor v : vtr) v.info();
}
};
// Базовый класс CompanyBuilder объявляет интерфейс для поэтапного
// построения предприятия и предусматривает его реализацию по умолчанию
class CompanyBuilder
{
protected:
Company* p;
public:
CompanyBuilder() : p(0) {}
virtual ~CompanyBuilder() {}
virtual void createCompany() {}
virtual void buildVaz() {}
virtual void buildUaz() {}
virtual void buildLimo() {}
virtual void buildTruck() {}
virtual void buildTractor() {}
virtual Company* getCompany() { return p; }
};
// таксопарк имеет все кроме грузовиков и тракторов
17
class TaxiBuilder : public CompanyBuilder
{
public:
void createCompany() { p = new Company; }
void buildVaz() { p->vv.push_back(Vaz()); }
void buildUaz() { p->vu.push_back(Uaz()); }
void buildLimo() { p->vl.push_back(Limo()); }
};
// Автоколонна имеет все кроме ВАЗ и лимузинов
class ParkBuilder : public CompanyBuilder
{
public:
void createCompany() { p = new Company; }
void buildUaz() { p->vu.push_back(Uaz()); }
void buildTruck() { p->vt.push_back(Truck()); }
void buildTractor() { p->vtr.push_back(Tractor()); }
};
// Класс-распорядитель, поэтапно создающий таксопарк или автоколонну
// Именно здесь определен алгоритм построения компании
class Director
{
public:
Company* createCompany(CompanyBuilder & builder)
{
builder.createCompany();
builder.buildVaz();
builder.buildUaz();
builder.buildLimo();
builder.buildTruck();
builder.buildTractor();
return(builder.getCompany());
}
};
int main()
{
Director dir;
TaxiBuilder tx_builder;
ParkBuilder pk_builder;
Company *tx = dir.createCompany(tx_builder);
Company *pk = dir.createCompany(pk_builder);
setlocale(LC_ALL, "rus");
cout << "Таксопарк" << endl;
tx->info();
cout << endl << "Автоколонна:" << endl;
pk->info();
return 0;
}
Очень часто базовый класс строителя (в коде выше это CompanyBuilder) не только
объявляет интерфейс для построения частей продукта, но и определяет ничего не делающую
18
реализацию по умолчанию. Тогда соответствующие подклассы (TaxiBuilder, ParkBuilder)
переопределяют только те методы, которые участвуют в построении текущего объекта. Так
класс TaxiBuilder не определяет методы buildTruck() и buildTractor(), поэтому таксопарк не
может иметь грузовики и тракторы. А в классе ParkBuilder не определены buidLimo() и
buildVaz(), поэтому автоколонна не может иметь ВАЗ и лимузин.
Интересно сравнить приведенный код с кодом создания компании в реализации
паттерна Abstract Factory, который также может использоваться для создания сложных
продуктов. Если паттерн Abstract Factory акцентирует внимание на создании семейств
некоторых объектов, то паттерн Builder подчеркивает поэтапное построение продукта. При
этом класс Builder скрывает все подробности построения сложного продукта так, что класс
Director ничего не знает о его составных частях.
Результаты применения паттерна Builder
Достоинства паттерна Builder
• Возможность контролировать процесс создания сложного продукта.
• Возможность получения разных представлений некоторых данных.
Недостатки паттерна Builder
• ConcreteBuilder и создаваемый им продукт жестко связаны между собой, поэтому при
внесении изменений в класс продукта скорее всего придется соответствующим
образом изменять и класс ConcreteBuilder.
Паттерн Prototype (прототип)
Создает новые объекты с помощью прототипов. Прототип - некоторый объект, умеющий
создавать по запросу копию самого себя.
Назначение паттерна Prototype
Паттерн Prototype (прототип) можно использовать в следующих случаях:
• Система должна оставаться независимой как от процесса создания новых объектов,
так и от типов порождаемых объектов. Непосредственное использование выражения
19
new в коде приложения считается нежелательным (подробнее об этом в разделе
Порождающие паттерны).
• Необходимо создавать объекты, точные классы которых становятся известными уже
на стадии выполнения программы.
Паттерн Factory Method также делает систему независимой от типов порождаемых объектов,
но для этого он вводит параллельную иерархию классов: для каждого типа создаваемого
объекта должен присутствовать соответствующий класс-фабрика, что может быть
нежелательно. Паттерн Prototype лишен этого недостатка.
Описание паттерна Prototype
Для создания новых объектов паттерн Prototype использует прототипы. Прототип это уже существующий в системе объект, который поддерживает операцию клонирования,
то есть умеет создавать копию самого себя. Таким образом, для создания объекта некоторого
класса достаточно выполнить операцию clone() соответствующего прототипа.
Паттерн Prototype реализует подобное поведение следующим образом: все классы,
объекты которых нужно создавать, должны быть подклассами одного общего абстрактного
базового класса. Этот базовый класс должен объявлять интерфейс метода clone(). Также
здесь могут объявляться виртуальными и другие общие методы, например, initialize() в
случае, если после клонирования нужна инициализация вновь созданного объекта. Все
производные классы должны реализовывать метод clone(). В языке С++ для создания копий
объектов используется конструктор копирования, однако, в общем случае, создание объектов
при помощи операции копирования не является обязательным.
Для порождения объекта некоторого типа в системе должен существовать его
прототип. Прототип представляет собой объект того же типа, единственным назначением
которого является создание подобных ему объектов. Обычно для удобства все
существующие в системе прототипы организуются в специальные коллекции-хранилища или
реестры прототипов. Такое хранилище может иметь реализацию в виде ассоциативного
массива, каждый элемент которого представляет пару "Идентификатор типа" - "Прототип".
Реестр прототипов позволяет добавлять или удалять прототип, а также создавать объект по
идентификатору типа. Именно операции динамического добавления и удаления прототипов
в хранилище обеспечивают дополнительную гибкость системе, позволяя управлять
процессом создания новых объектов.
Также как и для паттерна Factory Method приведем две возможные реализации паттерна
Prototype, а именно:
1. В виде обобщенного конструктора на основе прототипов, когда в полиморфном
базовом классе Prototype определяется статический метод, предназначенный для
создания объектов. При этом в качестве параметра в этот метод должен передаваться
идентификатор типа создаваемого объекта. App56
20
#include "pch.h"
// ConsoleApplication56.cpp : Реализация паттерна Ptototype на основе обобщенного
конструктора
//
#include
#include
#include