Document 5026973

advertisement
Назначение паттерна «Прототип»
 Задает виды создаваемых объектов с помощью
экземпляра-прототипа и создает новые объекты
путем копирования этого прототипа
 Подобно «Абстрактной фабрике» и «Строителю»
паттерн «Прототип» скрывает от клиента
конкретные классы продуктов, уменьшая число
известных клиенту имен
Структура
Client
Объявляет интерфейс
для клонирования
самого себя
Prototype
Operation()
Clone()
Реализует операцию
клонирования себя
Создает новый объект,
обращаясь к прототипу с
запросом клонировать себя
ConcretePrototype1
Clone()
Вернуть копию самого себя
ConcretePrototype2
Clone()
Вернуть копию самого себя
Отношения между участниками
паттерна
 Клиент обращается к прототипу, чтобы тот
создал свою копию
Применимость паттерна
«Прототип»
 Система не должна зависеть от того, как в ней
создаются, компонуются и представляются
продукты
 Инстанцируемые классы определяются во время
выполнения

Например, с помощью динамической загрузки
 Избежание построения иерархий классов или
фабрик, параллельных иерархии классов продуктов
 Экземпляры класса могут находиться в одном из не
очень большого числа различных состояний

Может оказаться удобнее задать соответствующее число
прототипов и клонировать их
Преимущества использования
паттерна «Прототип»
 Добавление и удаление продуктов во время
выполнения
 Клиенту просто сообщается о новом экземпляре-
прототипе
 Спецификация новых объектов путем изменения
значений
 Склонированный и слегка модифицированный
экземпляр прототипа может быть также
зарегистрирован в роли прототипа
Преимущества использования
паттерна «Прототип»
 Специфицирование новых объектов путем изменения
структуры
 Уменьшение числа подклассов
 Фабричный метод часто порождает иерархию классов
«Создатель», параллельную иерархии классов продуктов

Паттерн «прототип» может клонировать прототип, а не
запрашивать фабричный метод
 Динамическое конфигурирование приложения
 Динамически загружаемые классы прототипов
регистрируют свои экземпляры в «диспетчере
прототипов»
Пример использования паттерна
«Прототип»
 Палитра готовых фигур
в программах вроде
Microsoft PowerPoint
 Пользователь может
создавать фигуры на
основе имеющихся
прототипов фигур
 Количество
прототипов фигур
потенциально может
быть неограниченным
Иерархия
Абстрактный прототип
CShape
Clone()
CRectangle
Clone()
CEllipse
Clone()
Конкретные прототипы
CCallout
Clone()
Исходный код прототипов
class CShape
{
protected:
CShape(float l, float t, float w, float h)
{/*...*/}
public:
virtual ~CShape(){}
virtual CShape* Clone()const = 0;
float GetWidth()const{/*...*/}
float GetHeight()const{/*...*/}
float GetLeft()const{/*...*/}
float GetTop()const{/*...*/}
};
typedef boost::shared_ptr<CShape> CShapePtr;
class CEllipse : public CShape
{
public:
CEllipse(float l, float t, float w, float h)
:CShape (l, t, w, h){/*...*/}
virtual CShape* Clone()const
{return new CEllipse(*this);}
};
class CRectangle : public CShape
{
public:
CRectangle(float l, float t, float w, float h)
:CShape (l, t, w, h){/*...*/}
virtual CShape* Clone()const
{
return new CRectangle(*this);
}
};
class CCallout : public CShape
{
public:
CCallout(float l, float t, float w, float h, float adjX, float adjY)
:CShape (l, t, w, h){}
virtual CShape* Clone()const
{
return new CCallout(*this);
}
float GetAdjustmentPointX(){/*...*/}
float GetAdjustmentPointY(){/*...*/}
};
Диспетчер прототипов фигур
class CShapeManager
{
public:
void RegisterShapePrototype(std::string const& id, CShapePtr pShape)
{
m_prototypes[id] = pShape;
}
CShapePtr CreateShape(std::string const& id)const
{
PrototypeMap::const_iterator it = m_prototypes.find(id);
if (it == m_prototypes.end())
{
throw std::invalid_argument(“Unknown shape type");
}
// создание фигуры на основе найденного прототипа
return CShapePtr(it->second->Clone());
}
private:
typedef std::map<std::string, CShapePtr> PrototypeMap;
PrototypeMap m_prototypes;
};
Использование прототипов
int main(int argc, char * argv[])
{
CShapeManager shapes;
// регистрируем прототипы фигур в менеджере прототипов при старте приложения
shapes.RegisterShapePrototype("Ellipse", CShapePtr(new CEllipse(0, 0, 10, 10)));
shapes.RegisterShapePrototype("Rectangle", CShapePtr(new CRectangle(0, 0, 10, 10)));
shapes.RegisterShapePrototype("Callout", CShapePtr(new CCallout(0, 0, 10, 10, 5, 15)));
// ...
// создаем выноску при помощи диспетчера прототипов
std::string shapeType = “Callout”;
CShapePtr pShape = shapes.CreateShape(shapeType);
// ...
return 0;
}
Недостатки паттерна прототип
 Каждый подкласс класса Prototype должен
реализовывать операцию Clone
 Для уже существующих классов реализация
операции клонирования может быть
проблематичной
 В ряде случаев задача «глубокого» клонирования
может быть нетривиальной


Во внутреннем представлении объекта содержатся другие
объекты
Внутри объекта присутствуют круговые ссылки.
Назначение паттерна
«Одиночка»
 Гарантирует, что у класса есть только один
экземпляр, и предоставляет к нему глобальную
точку доступа
 Приложению может потребоваться одна-
единственная фабрика компонентов
пользовательского интерфейса
 Приложению может потребоваться однаединственная база данных
Применимость
 Должен быть ровно один экземпляр некоторого
класса, легко доступный всем клиентам
 Единственный экземпляр должен расширяться
путем порождения подклассов, и клиентам нужно
иметь возможность работать с расширенным
экземпляром без модификации своего кода
Структура
Singleton
static Instance()
SingletonOperation()
GetSingletonData()
static uniqueInstance
singletonData
return uniqueInstance
•Определяет операцию Instance()
(статический метод в C++), которая
позволяет клиентам получать доступ
к единственному экземпляру
•Может нести ответственность за
создание собственного уникального
экземпляра
Отношения
 Клиенты получают доступ к экземпляру класса
Singleton только через его операцию Instance
Достоинства
 Контролируемый доступ к единственному
экземпляру
 Уменьшение числа имен по сравнению с
глобальными переменными
 Допускает уточнение операций и представления
 От класса Singleton можно порождать подклассы
 Допускает переменное число экземпляров
 Необходимо лишь изменить операцию Instance
 Большая гибкость, чем у статических функций
класса
 В C++ статические функции не могут быть
виртуальными => нельзя использовать
полиморфизм
Простейшая реализация
template<class T> class CSingleton : public boost::non_copyable
{
public:
static T& Instance()
{
// у класса T есть конструктор по умолчанию
static T theSingleInstance;
return theSingleInstance;
}
};
class COnlyOne : public CSingleton<COnlyOne>
{
friend class CSingleton<COnlyOne>
COnlyOne(){}
// закрытый конструктор по умолчанию
//.. интерфейс класса
};
int main()
{
COnlyOne & one = COnlyOne::Instance();
// использование экземпляра one
}
Thread-safe реализация паттерна
«Одиночка»
#include <boost/utility.hpp>
#include <boost/thread/once.hpp>
#include <boost/scoped_ptr.hpp>
// Warning: If T's constructor throws, instance() will return a null reference.
template<class T> class CSingleton : private boost::noncopyable
{
public:
static T& instance()
{
boost::call_once(init, flag);
return *t;
}
static void init() // never throws
{
t.reset(new T());
}
protected:
~CSingleton() {}
CSingleton() {}
private:
static boost::scoped_ptr<T> t;
static boost::once_flag flag;
};
template<class T> boost::scoped_ptr<T> CSingleton<T>::t(0);
template<class T> boost::once_flag CSingleton<T>::flag = BOOST_ONCE_INIT;
Пример использования
#include <boost/utility.hpp>
#include <boost/thread/once.hpp>
#include <boost/scoped_ptr.hpp>
// Warning: If T's constructor throws, instance() will return a null
reference.
template<class T> class Singleton : private boost::noncopyable
{
public:
class CMyClass : public CSingleton<CMyClass>
static T& Instance()
{
{
friend class CSingleton<CMyClass>;
boost::call_once(Init, flag);
public:
return *t;
void DoSomething()
}
{
static void Init() // never throws
std::cout << "Something";
{
}
t.reset(new T());
private:
}
CMyClass();
protected:
};
~Singleton() {}
void test()
Singleton() {}
{
private:
CMyClass::Instance().DoSomething();
static boost::scoped_ptr<T> t;
}
static boost::once_flag flag;
};
template<class T> boost::scoped_ptr<T> Singleton<T>::t(0);
template<class T> boost::once_flag Singleton<T>::flag = BOOST_ONCE_INIT;
Особенности реализации
паттерна «Одиночка» в C++
 В C++ не определяется порядок вызова
конструкторов для глобальный объектов через
границы единиц трансляции
 Между одиночками не может существовать никаких
зависимостей
Способы параметризации системы
классами создаваемых объектов
 Порождение подклассов, от класса, создающего
объекты
 Фабричный метод
 Композиция объектов
 Параметром системы является класс (фабричный
объект), которому известно о классах объектовпродуктов



Абстрактная фабрика
Строитель
Прототип
Паттерн «Прототип»
 Лучше всего подходит для каркасов визуальных
графических редакторов
 Сокращает число подклассов, создающих объекты
 Метод Clone() можно использовать также для
создания дубликатов объектов
Паттерн «Фабричный метод»
 В классе-наследнике требуется лишь
переопределить операцию инстанцирования
продукта
 Не следует использовать, если инстанцируемый
класс никогда не изменяется или инстанцирование
выполняется внутри операции, которую можно
заместить в подклассах
Абстрактная фабрика, прототип,
строитель
 Плюсы:
 Проекты, использующие данные паттерны, являются
более гибкими
 Минусы:
 Повышенная сложность
Структурные паттерны
 Определяют различные сложные структуры,
изменяющие интерфейс существующих объектов
или его реализацию
 Адаптер (Adapter)
 Мост (Bridge)
 Компоновщик (Composite)
 Декоратор (Decorator)
 Фасад (Facade)
 Приспособленец (Flyweight)
 Заместитель (Proxy)
Паттерн Adapter
 Преобразует интерфейс одного класса в интерфейс
другого, который ожидают клиенты
 Обеспечивает совместную работу классов с
несовместимыми интерфейсами, которая без него
была бы невозможна
 Альтернативное название – Wrapper (Обертка)
 Типы:
 Адаптер класса
 Адаптер объекта
Применимость
 Необходимо использовать существующий класс, но
его интерфейс не соответствует заданным
требованиям
 Создание повторно используемого класса, который
должен взаимодействовать с заранее
неизвестными или не связанными с ним классами,
имеющими несовместимые интерфейсы
 Использование нескольких существующих
подклассов, приспосабливая интерфейс их общего
родительского класса (только для адаптера
объектов)
Структура адаптера класса
Client
Target
Adaptee
Request()
SpecificRequest()
Взаимодействует с
объектами,
удовлетворяющими
интерфейсу Target
Определяет зависящий
от предметной области
интерфейс, которым
пользуется Client
Определяет
существующий
интерфейс,
который нуждается
в адаптации
(реализация)
Adapter
Request()
Адаптирует
интерфейс Adaptee
к интерфейсу Target
SpecificRequest()
Структура адаптера объектов
Client
Target
Adaptee
Request()
Adapter
Request()
SpecificRequest()
adaptee
adaptee->SpecificRequest()
Отношения (адаптер класса)
 Адаптер класса адаптирует Adaptee к Target,
перепоручая действия конкретному классу Adaptee
 Этот паттерн не будет работать, если мы захотим
одновременно адаптировать класс и его подклассы
 Позволяет адаптеру Adapter заместить некоторые
операции адаптируемого класса Adaptee
 Вводит только один новый объект
 Для того, чтобы добраться до адаптируемого класса,
не нужно дополнительного обращения по указателю
Отношения (адаптер объектов)
 Позволяет одному адаптеру Adapter работать со
многими адаптируемыми объектами Adaptee
 С самим Adaptee и его подклассами при их наличии
 Адаптер может добавить новую функциональность
сразу всем адаптируемым объектам
 Затрудняет замещение операций класса Adaptee
 Для этого необходимо породить от Adaptee подкласс
и заставить Adapter ссылаться на этот подкласса, а не
на сам Adaptee
Вопросы, которые необходимо
иметь в виду
 Объем работы
 Зависит от того, насколько сильно различаются
интерфейсы целевого и адаптируемого классов
 Сменные адаптеры
 Решается путем адаптации интерфейсов
 Использование двусторонних адаптеров для
обеспечения прозрачности
 Полезны в тех случаях, когда необходимо клиенту
необходимо видеть как адаптируемый, так и целевой
объект
Реализация адаптеров классов
 В C++ Adapter должен открыто наследоваться от
Target и закрыто – от Adaptee
 Adapter – подтип Target, но не Adaptee
class CTarget
{
public:
virtual void DoSomething() = 0;
};
class CAdaptee
{
public:
void DoSomethingGood();
};
class CAdapter : public CTarget, private CAdaptee
{
public:
virtual void DoSometing()
{
DoSomethingGood();
}
};
Пример – иерархия графических
объектов
class CPoint
{
public:
int x, int y
};
class CShape
{
public:
virtual void GetBoundingBox(CPoint & bottomLeft, CPoint & topRight)const
};
class CTextView
{
public:
int GetLeft()const;
int GetTop()const;
int GetWidth()const;
int GetHeight()const;
};
Задача – добавить в иерархию класс
CTextShape (наследник CShape),
используя функциональность класса
CTextView
Решение
class CPoint
{
public:
…
int x, int y
};
class CShape // целевой объект
{
public:
virtual void GetBoundingBox(CPoint & bottomLeft, CPoint & topRight)const;
};
class CTextView {…};
// адаптируемый класс
// адаптер
class CTextShape : public CShape, private CTextView
{
public:
virtual void GetBoundingBox(CPoint & bottomLeft, CPoint & topRight)const
{
bottomLeft.x = GetLeft();
bottomLeft.y = GetTop() + GetHeight();
topRight.x = GetLeft() + GetWidth();
topRight.y = GetTop();
}
};
Реализация сменных адаптеров
 Задача – разработать компонент CTreeDisplay для
визуализации древовидных структур
 Иерархии классов
 Дерево папок
 Иерархии живых организмов
 Для разных типов структур нужны разные операци
доступа к потомкам:
 GetSubclasses для классов, GetSubdirectories для файловой
системы, и т.п.
 Компонент CTreeDisplay должен уметь отображать
иерархии обоих видов даже если у них разные
интерфейсы
Реализация сменных адаптеров
 Шаг 1. Поиск «узкого» интерфейса для Adaptee
 Наименьшее подмножество операций, позволяющее
выполнить адаптацию

Минимальный интерфейс для CTreeDisplay может
включать всего две операции
 Получить графическое представление узла
 Доступ к потомкам узла
 Шаг 2. Выбор одного из следующих подходов к
реализации
 Использование абстрактных операций
 Использование объектов-уполномоченных
Подход 1 – «Использование
абстрактных операций»
 Определим в классе CTreeDisplay абстрактные
операции, соответствующие узкому интерфейсу
класса Adaptee
 Подклассы CTreeDisplay реализовывают данные
операции и адаптируют иерархически
структурированный объект


Класс CDirectoryTreeDisplay будет осуществлять доступ к
структуре каталогов файловой системы
Класс CDirectoryTreeDisplay специализирует узкий
интерфейс таким образом, чтобы он мог отображать
структуру каталогов, составленную из объектов
CFileSystemEntity
Структура
CTreeDisplay (Client, Target)
GetChildren(CNode)
CreateGraphicNode(CNode)
Display()
BuildTree(Node n)
GetChildren(n)
Для каждого потомка {
AddGraphicNode(CreateGraphicNode(child))
BuildTree(child)
}
DirectoryTreeDisplay (Adapter)
GetChildren(Node)
CreateGraphicNode(Node)
CFileSystemEntity (Adaptee)
Подход 2 – «Использование
объектов-уполномоченных»
 CTreeDisplay выполняет переадресацию запросов к
иерархической структуре объектууполномоченному
 Узкий интерфейс объекта-уполномоченного
(Целевой объект) помещается в абстрактный класс
CTreeAccessorDelegate
 Класс CDirectoryBrowser (Адаптер) наследуется от
CTreeAccessorDelegate, реализуя абстрактные
операции уполномоченного объекта
Структура
CTreeDisplay (Client)
CTreeAccessorDelegate (Target)
delegate GetChildren(CTreeDisplay, Node)
CreateGraphicNode(CTreeDisplay, Node)
SetDelegate(CTreeAccessorDelegate)
Display()
BuildTree(Node n)
CDirectoryBrowser (Adapter)
delegate->GetChildren(this, n)
Для каждого потомка {
AddGraphicNode(
delegate->CreateGraphicNode(this, child))
BuildTree(child)
}
GetChildren(CTreeDisplay, Node)
CreateGraphicNode(CTreeDisplay, Node)
CreateFile()
DeleteFile()
CFileSystemEntity (Adaptee)
Download