|
|
Схемы и диаграммы не видны на слайде полностью. Скрыта нижняя часть.
Паттерны проектирования (Design patterns)
Composite (Компоновщик)
Паттерн «Компоновщик» Компонует объекты в древовидные структуры для представления иерархий «часть-целое» Позволяет клиентам единообразно трактовать индивидуальные и составные объекты
Применимость паттерна «Компоновщик» Представление иерархии объектов вида «часть-целое» Клиенты должны однообразно трактовать составные и индивидуальные объекты
Структура Client Component Operation() Add(Component) Remove(Component) GetChild(int) Для каждого потомка g g.Operation() Определяет интерфейс для компонуемых объектов Предоставляет подходящую для всех классов реализацию операций по умолчанию Объявляет интерфейс для доступа к потомкам и управления ими Определяет интерфейс для доступа к родителю компонента в рекурсивной структуре и при необходимости реализует его (опциональная функция) Потомки Представляет листовые узлы композиции и не имеет потомков Определяет поведение примитивных объектов в композиции Определяет поведение компонентов, у которых есть потомки Хранит компоненты-потомки Реализует операции управления потомками, определенные в интерфейсе класса Component Манипулирует объектами композиции через интерфейс Component
Отношения Клиенты используют интерфейс класса Component для взаимодействия с объектами в составной структуре Если получателем запроса является объект Leaf, то он и обрабатывает запрос Если получателем запроса является объект Composite, то обычно он перенаправляет запрос своим потомкам До или после перенаправления могут выполняться дополнительные операции
Результаты Определяются иерархии классов, состоящие из примитивных и составных объектов Из примитивных объектов могут создаваться более сложные, и т.д. Упрощение архитектуры клиента Клиенты могут единообразно работать с индивидуальными и объектами и с составными структурами Облегчается добавление новых видов компонентов При добавление новых компонентов нет необходимости изменять код клиента Создание общего дизайна (архитектуры)
Ограничения возможностей паттерна «Компоновщик» Для того, чтобы составной объект мог включать только определенные виды компонентов, приходится прибегать к проверкам во время выполнения программы Паттерн «Компоновщик» не позволяет воспользоваться для реализации таких ограничений статической системой типов
Ссылки на родителей Хранение ссылки на своего родителя упрощает обход и обработку структуры Облегчается обход вверх по структуре и удаление компонента Необходимость соблюдения инварианта: Если объект A ссылается на объект B как на своего родителя, то для последнего объект A является потомком Простейший способ реализации – модификация ссылки на родителя только при операции Add и Remove
Разделение компонентов Для уменьшения потребления памяти возможно использование одного компонента в составе нескольких составных объектов Возможная проблема – неоднозначность при определении родительского узла Composite 1 Composite 2 Composite 3 A B C E F D G H I J
Максимизация интерфейса класса Component Цель паттерна «Компоновщик» - избавить клиентов от необходимости знать, работают они с листовым или составным объектом С одной стороны, базовый класс Component должен содержать как можно больше операций общих для Leaf и Composite С другой стороны, базовый класс должен определять только логичные для всех его подклассов операции Для класса Leaf многие операции класса Composite не имеют смылса Имеет смысл рассматривать Leaf как Component, у которого никогда не будет потомков
Объявление операций для управления потомками Есть 2 варианта объявления операция управления потомками В корне иерархии (в классе Component) Достоинство – прозрачность работы с потомками для клиентов Недостаток – низкая безопасность – клиент может попытаться добавить или удалить объект из листового узла В классе Composite Достоинство – повышается безопасность Недостаток – утрата прозрачности (у составного и листового объектов разные интерфейсы) – приходится прибегать к операторам приведения типов (небезопасная операция)
Компромисс Объявить в базовом классе Component операцию Composite* GetComposite() По умолчанию возвращает NULL (для листьев) Composite возвращает указатель на самого себя Недостаток: частичная потеря прозрачности (приходится проверять тип объекта) Единственный способ обеспечения прозрачности – объявить все операции работы с потомками в базовом классе Недостаток – нет разумной реализации для операции Add
Удаление дочерних компонентов При использовании языка программирования, не имеющего сборщика мусора, удаление потомков лучше всего поручить классу Composite в момент своего уничтожения В C++ имеет смысл рассмотреть использование умных указателей
Выбор структуры данных для хранения компонентов При выборе структуры данных следует руководствоваться эффективностью в данном случае
Пример использования В программах вроде Corel Draw, Visio или PowerPoint пользователь может создавать сложные объекты, группируя их из простых или сложных объектов Над полученными составными объектами возможно выполнять те же операции, что и над простыми Использование паттерна «Компоновщик» облегчает решение данной задачи
Иерархия классов
Исходный код класса CGraphic (компонент) typedef boost::shared_ptr<class CGraphic> CGraphicPtr; class CGraphic { public: CGraphic() :m_pParent(NULL) { } virtual float GetLeft()const = 0; virtual float GetTop()const = 0; virtual float GetWidth()const = 0; virtual float GetHeight()const = 0; virtual void Draw()const = 0; virtual CGroup* GetGroup(){return NULL;} CGraphic * GetParent()const {return m_pParent;} void SetParent(CGraphic * pParent){ m_pParent = pParent;} private: CGraphic * m_pParent; }; Операции над объектом Доступ к составному объекту Операции доступа к родителю
Обобщенная реализация примитивного объекта template <class Base> class CPrimitiveImpl : public Base { public: CPrimitiveImpl(float l, float t, float w, float h) :m_left(l), m_top(t), m_width(w), m_height(h){} virtual float GetLeft()const{return m_left;} virtual float GetTop()const{return m_top;} virtual float GetWidth()const{return m_width;} virtual float GetHeight()const{return m_height;} private: float m_left, m_top, m_width, m_height; }; Абстрактный метод Draw будет реализован в подклассах
Конкретные реализации листовых объектов class CRectangle : public CPrimitiveImpl<CGraphic> { public: CRectangle(float l, float t, float w, float h) :CPrimitiveImpl(l, t, w, h){} virtual void Draw()const { // рисуем прямоугольник } }; class CEllipse : public CPrimitiveImpl<CGraphic> { public: CEllipse(float l, float t, float w, float h) :CPrimitiveImpl(l, t, w, h){} virtual void Draw()const { // рисуем эллипс } };
Реализация составного объекта CGroup (начало) class CGroup : public CGraphic { std::vector<CGraphicPtr> m_children; public: virtual CGroup * GetGroup(){return this;} virtual size_t Add(CGraphicPtr pGraphic) { m_children.push_back(pGraphic); pGraphic->SetParent(this); return m_children.size() - 1; } virtual size_t GetChildCount()const { return m_children.size(); } virtual CGraphicPtr GetChild(size_t index)const { return m_children.at(index); } virtual void Remove(size_t index) { if (index >= GetChildCount()) throw std::out_of_range("Invalid child index"); m_children.erase(m_children.begin() + index); } … Операции доступа к потомкам
Реализация составного объекта CGroup (продолжение) virtual void Draw()const { for (size_t i = 0; i < GetChildCount(); ++i) GetChild(i)->Draw(); } virtual float GetLeft()const { if (m_children.empty()) throw std::logic_error("Empty groups are not allowed"); float left = GetChild(0)->GetLeft(); for (size_t i = 1; i < GetChildCount(); ++i) left = std::min(left, GetChild(i)->GetLeft()); return left; } virtual float GetTop()const { if (m_children.empty()) throw std::logic_error("Empty groups are not allowed"); float top = GetChild(0)->GetTop(); for (size_t i = 1; i < GetChildCount(); ++i) top = std::min(top, GetChild(i)->GetTop()); return top; } … Графические операции
Реализация составного объекта CGroup (окончание) virtual float GetWidth()const { if (m_children.empty()) return 0; float right = GetChild(0)->GetLeft() + GetChild(0)->GetWidth(); for (size_t i = 1; i < GetChildCount(); ++i) { right = std::max(right, GetChild(i)->GetLeft() + GetChild(i)->GetWidth()); } return right - GetLeft(); } virtual float GetHeight()const { if (m_children.empty()) return 0; float bottom = GetChild(0)->GetTop() + GetChild(0)->GetHeight(); for (size_t i = 1; i < GetChildCount(); ++i) { bottom = std::max(bottom, GetChild(i)->GetTop() + GetChild(i)->GetHeight()); } return bottom - GetTop(); } }; Графические операции
Пример использования int main(int argc, char * argv[]) { CGraphicPtr pGroupedShape(new CGroup()); CGraphicPtr pRectangle(new CRectangle(5, 5, 20, 15)); CGraphicPtr pEllipse(new CEllipse(-5, -5, 15, 15)); CGroup * pGroup = pGroupedShape->GetGroup(); if (pGroup != NULL) { pGroupedShape->Add(pRectangle); pGroupedShape->Add(pEllipse); } std::cout << "Group left coord: " << pGroupedShape->GetLeft() << "\n"; std::cout << "Group top coord: " << pGroupedShape->GetTop() << "\n"; std::cout << "Group width: " << pGroupedShape->GetWidth() << "\n"; std::cout << "Group height: " << pGroupedShape->GetHeight() << "\n"; return 0; } Output: Group left coord: -5 Group top coord: -5 Group width: 30 Group height: 25
Decorator (Декоратор)
Паттерн «Декоратор» Динамически добавляет объекту новые обязанности Является гибкой альтернативой порождению подклассов с целью расширения функциональности Альтернативное имя – Wrapper (обертка)
Применимость Динамическое, прозрачное для клиентов добавление обязанностей объектам Реализация обязанностей, которые могут быть сняты с объекта Случаи, когда расширение с помощью подклассов неудобно или невозможно Комбинаторный рост числа подклассов при большом количестве независимых расширений Недоступность определения класса
Структура Decorator::Operation(); AddedBehavior(); component Определяет интерфейс для объектов, на которые могут быть возложены дополнительные обязанности component->Operation() Определяет объект, на который возлагаются дополнительные обязанности Хранит ссылку на объект Component и определяет интерфейс, соответствующий интерфейсу Component Возлагает дополнительные обязанности на компонент
Отношения Decorator переадресует запросы объекту Component Может выполнять дополнительные операции до и после переадресации
Достоинства паттерна «Декоратор» Большая гибкость, нежели у статического наследования Возможность добавления и удаления новых обязанностей во время выполнения программы При наследовании требуется создавать новый класс для каждой дополнительной обязанности Возможность произвольного сочетания обязанностей путем применения нескольких декораторов к одному компоненту Позволяет избежать перегруженных функциями классов на верхних уровнях иерархии Новые обязанности добавляются по мере необходимости Возможность постепенного добавления новых функций
Недостатки паттерна «Декоратор» Декоратор и его компонент не идентичны Несмотря на то, что декоратор действует как прозрачное обрамление, декорированный компонент не идентичен исходному Возникновение большого количества мелких объектов Объекты различаются способом взаимосвязи Сложность в изучение и отладке
Особенности реализации Интерфейс декоратора должен соответствовать интерфейсу декорируемого компонента Классы ConcreteDecorator должны наследовать общему классу Базовый класс Component должен быть максимально легким Иначе декораторы могут стать тяжеловесными Декоратор – оболочка объекта, изменяющая его поведение
Пример использования В программе для показа слайд шоу необходимо предоставить возможность добавлять в кадр различные спецэффекты Рамка у изображения Падающие снежинки Рыбки Огонь и т.п. Эффекты можно комбинировать произвольным образом
Структура Компонент Декоратор Конкретный компонент Конкретный декоратор
Исходный код // Компонент class CGraphic { public: virtual void Draw()const = 0; }; typedef boost::shared_ptr<CGraphic> CGraphicPtr; // Конкретный компонент class CPicture : public CGraphic { public: virtual void Draw()const {/* Рисуем картинку*/} }; // Декоратор class CVisualEffect : public CGraphic { public: CVisualEffect(CGraphicPtr pGraphic) :m_pGraphic(pGraphic) {} virtual void Draw()const {m_pGraphic->Draw();} private: CGraphicPtr m_pGraphic; }; class CFireEffect : public CVisualEffect { public: CFireEffect(CGraphicPtr pGraphic) :CVisualEffect(pGraphic) {} virtual void Draw()const { CVisualEffect::Draw(); // рисуем пламя } }; class CBorderEffect : public CVisualEffect { public: CBorderEffect(CGraphicPtr pGraphic) :CVisualEffect(pGraphic) { } virtual void Draw()const { CVisualEffect::Draw(); // рисуем рамку } };
Пример использования int main(int argc, char * argv[]) { CGraphicPtr pPicture(new CPicture()); CGraphicPtr pPictureInFire(new CFireEffect(pPicture)); CGraphicPtr pBorderedPictureInFire(new CBorderEffect(pPictureInFire)); pBorderedPictureInFire->Draw(); return 0; }
Еще пример Возможности паттерна не ограничиваются визуальными приложениями Пример – наделение потоков ввода-вывода новыми возможностями Сжатие выводимых данных Преобразование данных в формат ASCII7 либо Base64
Структура component component->HandleBufferFull() Сжать данные в буфере StreamDecorator::HandleBufferFull()
Proxy (Заместитель)
Паттерн «Заместитель» Является суррогатом другого объекта и контролирует доступ к нему Альтернативное имя – Surrogate (суррогат)
Применимость Удаленный заместитель (посол) Предоставляет локального представителя вместо объекта в другом адресном пространстве Виртуальный заместитель Создание «тяжелых» объектов по требованию Защищающий заместитель Контроль доступа к исходному объекту Умный указатель Подсчет ссылок, управление временем жизни Загрузка объекта в память при первом обращении к нему Блокировка доступа к объекту при обращении к нему
Структура ... realSubject->Request(); ... realSubject Client Хранит ссылку на реального субъекта Предоставляет интерфейс, идентичный интерфейсу Subject – всегда может быть использован вместо реального субъекта Контролирует доступ к реальному субъекту, может отвечать за его создание и удаление Удаленный заместитель – кодирует и отправляет запрос реальному субъекту Виртуальный заместитель – кэширует доп. информацию о реальному субъекте Защищающий заместитель – проверяет необходимые права Определяет общий для RealSubject и Proxy интерфейс, так что класс Proxy можно использовать везде, где ожидается RealSubject Определяет реальный субъект, представленный заместителем
Отношения Proxy при необходимости выполняет переадресацию запросов объекту RealSubject Детали переадресации зависят от вида заместителя Клиенты взаимодействуют с субъектом только через интерфейс Subject
Результаты Для доступа к объекту вводится дополнительный уровень косвенности. Варианты: Удаленный заместитель может скрыть факт, что объект находится в другом адресном пространстве Виртуальный заместитель может выполнять оптимизацию, например, создавать объект по требованию Защищающий заместитель и «умный» указатель решают дополнительные задачи при доступе к объекту Оптимизация Copy-On-Write (копирование при записи)
Оптимизация Copy-on-write Копирование большого и сложного объекта – очень дорогая операция Если объект не изменяется, все его копии - идентичны Копирование можно отложить до момента действительной модификации объекта Proxy ведет подсчет ссылок на объект Копирование proxy увеличивает счетчик ссылок, разрушение - уменьшает Выполняя операцию, изменяющую субъект, счетчик ссылок которого > 1, заместитель выполняет копирование объекта и уменьшает счетчик ссылок на оригинал Если счетчик обнулился – объект удаляется
Copy-on-write в действии Subject Proxy Proxy Client Proxy Copy 1 2 Modify Subject Subject 1 Modify
Пример использования Текстовый документ может содержать большое количество иллюстраций Загрузка и распаковка всех иллюстраций может занимать длительное время После открытия документа пользователь видит всего одну-две страницы Все иллюстрации документа ему сразу не видны – имеет смысл начинать загрузку иллюстрации только при необходимости ее нарисовать Важно сохранить форматирование документа даже при незагруженных изображениях Решение – временно подставлять в документ объект-заместитель вместо реального изображения Заместитель должен знать ширину и высоту изображения – они нужны для форматирования документа
Структура if (image == NULL) image = new CImage(fileName); image->Draw() image CDocumentEditor if (image == NULL) return extent; else return image->GetExtent()
Исходный код class CExtent { public: CExtent(float width = 0, float height = 0); float GetWidth()const; float GetHeight()const; }; class CGraphic // субъект { public: virtual void Draw()const = 0; virtual CExtent GetExtent()const = 0; }; typedef boost::shared_ptr<CGraphic> CGraphicPtr; class CImage : public CGraphic { // реальный субъект public: CImage(std::string const& fileName) {/* загружаем картинку из файла */} virtual void Draw()const {/*рисуем картинку*/} virtual CExtent GetExtent()const {/*возвращаем размеры картинки*/} }; class CImageProxy : public CGraphic // заместитель { public: CImageProxy(std::string const& fileName) :m_fileName(fileName) {/*считываем размеры изображения из заголовка файла*/} virtual void Draw()const { if (!m_pImage) m_pImage.reset(new CImage(m_fileName)); m_pImage->Draw(); } virtual CExtent GetExtent()const { if (!m_pImage) return m_extent; else return m_pImage->GetExtent(); } private: std::string m_fileName; CExtent m_extent; mutable CGraphicPtr m_pImage; };
Пример использования class CTextDocument { public: void Insert(CGraphicPtr pGraphic) { // вставляем графический объект в документ } void Draw() { // рисуем все видимые графические объекты внутри документа } }; int main(int argc, char * argv[]) { CTextDocument document; CGraphicPtr pImage(new CImageProxy("image1.jpg")); document.Insert(pImage); document.Draw(); return 0; }
Summary: В лекции рассказывается о паттернах проектирования "Компоновщик", "Декоратор", "Заместитель"
| URL: |
No comments posted yet
Comments