Лекция 10 - паттерны проектирования - part3

+3

No comments posted yet

Comments

xakxor (3 months ago)

Схемы и диаграммы не видны на слайде полностью. Скрыта нижняя часть.

Slide 1

Паттерны проектирования (Design patterns)

Slide 2

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

Slide 3

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

Slide 4

Применимость паттерна «Компоновщик» Представление иерархии объектов вида «часть-целое» Клиенты должны однообразно трактовать составные и индивидуальные объекты

Slide 5

Структура Client Component Operation() Add(Component) Remove(Component) GetChild(int) Для каждого потомка g g.Operation() Определяет интерфейс для компонуемых объектов Предоставляет подходящую для всех классов реализацию операций по умолчанию Объявляет интерфейс для доступа к потомкам и управления ими Определяет интерфейс для доступа к родителю компонента в рекурсивной структуре и при необходимости реализует его (опциональная функция) Потомки Представляет листовые узлы композиции и не имеет потомков Определяет поведение примитивных объектов в композиции Определяет поведение компонентов, у которых есть потомки Хранит компоненты-потомки Реализует операции управления потомками, определенные в интерфейсе класса Component Манипулирует объектами композиции через интерфейс Component

Slide 6

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

Slide 7

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

Slide 8

Ограничения возможностей паттерна «Компоновщик» Для того, чтобы составной объект мог включать только определенные виды компонентов, приходится прибегать к проверкам во время выполнения программы Паттерн «Компоновщик» не позволяет воспользоваться для реализации таких ограничений статической системой типов

Slide 9

Ссылки на родителей Хранение ссылки на своего родителя упрощает обход и обработку структуры Облегчается обход вверх по структуре и удаление компонента Необходимость соблюдения инварианта: Если объект A ссылается на объект B как на своего родителя, то для последнего объект A является потомком Простейший способ реализации – модификация ссылки на родителя только при операции Add и Remove

Slide 10

Разделение компонентов Для уменьшения потребления памяти возможно использование одного компонента в составе нескольких составных объектов Возможная проблема – неоднозначность при определении родительского узла Composite 1 Composite 2 Composite 3 A B C E F D G H I J

Slide 11

Максимизация интерфейса класса Component Цель паттерна «Компоновщик» - избавить клиентов от необходимости знать, работают они с листовым или составным объектом С одной стороны, базовый класс Component должен содержать как можно больше операций общих для Leaf и Composite С другой стороны, базовый класс должен определять только логичные для всех его подклассов операции Для класса Leaf многие операции класса Composite не имеют смылса Имеет смысл рассматривать Leaf как Component, у которого никогда не будет потомков

Slide 12

Объявление операций для управления потомками Есть 2 варианта объявления операция управления потомками В корне иерархии (в классе Component) Достоинство – прозрачность работы с потомками для клиентов Недостаток – низкая безопасность – клиент может попытаться добавить или удалить объект из листового узла В классе Composite Достоинство – повышается безопасность Недостаток – утрата прозрачности (у составного и листового объектов разные интерфейсы) – приходится прибегать к операторам приведения типов (небезопасная операция)

Slide 13

Компромисс Объявить в базовом классе Component операцию Composite* GetComposite() По умолчанию возвращает NULL (для листьев) Composite возвращает указатель на самого себя Недостаток: частичная потеря прозрачности (приходится проверять тип объекта) Единственный способ обеспечения прозрачности – объявить все операции работы с потомками в базовом классе Недостаток – нет разумной реализации для операции Add

Slide 14

Удаление дочерних компонентов При использовании языка программирования, не имеющего сборщика мусора, удаление потомков лучше всего поручить классу Composite в момент своего уничтожения В C++ имеет смысл рассмотреть использование умных указателей

Slide 15

Выбор структуры данных для хранения компонентов При выборе структуры данных следует руководствоваться эффективностью в данном случае

Slide 16

Пример использования В программах вроде Corel Draw, Visio или PowerPoint пользователь может создавать сложные объекты, группируя их из простых или сложных объектов Над полученными составными объектами возможно выполнять те же операции, что и над простыми Использование паттерна «Компоновщик» облегчает решение данной задачи

Slide 17

Иерархия классов

Slide 18

Исходный код класса 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; }; Операции над объектом Доступ к составному объекту Операции доступа к родителю

Slide 19

Обобщенная реализация примитивного объекта 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 будет реализован в подклассах

Slide 20

Конкретные реализации листовых объектов 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 { // рисуем эллипс } };

Slide 21

Реализация составного объекта 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); } … Операции доступа к потомкам

Slide 22

Реализация составного объекта 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; } … Графические операции

Slide 23

Реализация составного объекта 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(); } }; Графические операции

Slide 24

Пример использования 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

Slide 25

Decorator (Декоратор)

Slide 26

Паттерн «Декоратор» Динамически добавляет объекту новые обязанности Является гибкой альтернативой порождению подклассов с целью расширения функциональности Альтернативное имя – Wrapper (обертка)

Slide 27

Применимость Динамическое, прозрачное для клиентов добавление обязанностей объектам Реализация обязанностей, которые могут быть сняты с объекта Случаи, когда расширение с помощью подклассов неудобно или невозможно Комбинаторный рост числа подклассов при большом количестве независимых расширений Недоступность определения класса

Slide 28

Структура Decorator::Operation(); AddedBehavior(); component Определяет интерфейс для объектов, на которые могут быть возложены дополнительные обязанности component->Operation() Определяет объект, на который возлагаются дополнительные обязанности Хранит ссылку на объект Component и определяет интерфейс, соответствующий интерфейсу Component Возлагает дополнительные обязанности на компонент

Slide 29

Отношения Decorator переадресует запросы объекту Component Может выполнять дополнительные операции до и после переадресации

Slide 30

Достоинства паттерна «Декоратор» Большая гибкость, нежели у статического наследования Возможность добавления и удаления новых обязанностей во время выполнения программы При наследовании требуется создавать новый класс для каждой дополнительной обязанности Возможность произвольного сочетания обязанностей путем применения нескольких декораторов к одному компоненту Позволяет избежать перегруженных функциями классов на верхних уровнях иерархии Новые обязанности добавляются по мере необходимости Возможность постепенного добавления новых функций

Slide 31

Недостатки паттерна «Декоратор» Декоратор и его компонент не идентичны Несмотря на то, что декоратор действует как прозрачное обрамление, декорированный компонент не идентичен исходному Возникновение большого количества мелких объектов Объекты различаются способом взаимосвязи Сложность в изучение и отладке

Slide 32

Особенности реализации Интерфейс декоратора должен соответствовать интерфейсу декорируемого компонента Классы ConcreteDecorator должны наследовать общему классу Базовый класс Component должен быть максимально легким Иначе декораторы могут стать тяжеловесными Декоратор – оболочка объекта, изменяющая его поведение

Slide 33

Пример использования В программе для показа слайд шоу необходимо предоставить возможность добавлять в кадр различные спецэффекты Рамка у изображения Падающие снежинки Рыбки Огонь и т.п. Эффекты можно комбинировать произвольным образом

Slide 34

Структура Компонент Декоратор Конкретный компонент Конкретный декоратор

Slide 35

Исходный код // Компонент 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(); // рисуем рамку } };

Slide 36

Пример использования int main(int argc, char * argv[]) { CGraphicPtr pPicture(new CPicture()); CGraphicPtr pPictureInFire(new CFireEffect(pPicture)); CGraphicPtr pBorderedPictureInFire(new CBorderEffect(pPictureInFire)); pBorderedPictureInFire->Draw(); return 0; }

Slide 37

Еще пример Возможности паттерна не ограничиваются визуальными приложениями Пример – наделение потоков ввода-вывода новыми возможностями Сжатие выводимых данных Преобразование данных в формат ASCII7 либо Base64

Slide 38

Структура component component->HandleBufferFull() Сжать данные в буфере StreamDecorator::HandleBufferFull()

Slide 39

Proxy (Заместитель)

Slide 40

Паттерн «Заместитель» Является суррогатом другого объекта и контролирует доступ к нему Альтернативное имя – Surrogate (суррогат)

Slide 41

Применимость Удаленный заместитель (посол) Предоставляет локального представителя вместо объекта в другом адресном пространстве Виртуальный заместитель Создание «тяжелых» объектов по требованию Защищающий заместитель Контроль доступа к исходному объекту Умный указатель Подсчет ссылок, управление временем жизни Загрузка объекта в память при первом обращении к нему Блокировка доступа к объекту при обращении к нему

Slide 42

Структура ... realSubject->Request(); ... realSubject Client Хранит ссылку на реального субъекта Предоставляет интерфейс, идентичный интерфейсу Subject – всегда может быть использован вместо реального субъекта Контролирует доступ к реальному субъекту, может отвечать за его создание и удаление Удаленный заместитель – кодирует и отправляет запрос реальному субъекту Виртуальный заместитель – кэширует доп. информацию о реальному субъекте Защищающий заместитель – проверяет необходимые права Определяет общий для RealSubject и Proxy интерфейс, так что класс Proxy можно использовать везде, где ожидается RealSubject Определяет реальный субъект, представленный заместителем

Slide 43

Отношения Proxy при необходимости выполняет переадресацию запросов объекту RealSubject Детали переадресации зависят от вида заместителя Клиенты взаимодействуют с субъектом только через интерфейс Subject

Slide 44

Результаты Для доступа к объекту вводится дополнительный уровень косвенности. Варианты: Удаленный заместитель может скрыть факт, что объект находится в другом адресном пространстве Виртуальный заместитель может выполнять оптимизацию, например, создавать объект по требованию Защищающий заместитель и «умный» указатель решают дополнительные задачи при доступе к объекту Оптимизация Copy-On-Write (копирование при записи)

Slide 45

Оптимизация Copy-on-write Копирование большого и сложного объекта – очень дорогая операция Если объект не изменяется, все его копии - идентичны Копирование можно отложить до момента действительной модификации объекта Proxy ведет подсчет ссылок на объект Копирование proxy увеличивает счетчик ссылок, разрушение - уменьшает Выполняя операцию, изменяющую субъект, счетчик ссылок которого > 1, заместитель выполняет копирование объекта и уменьшает счетчик ссылок на оригинал Если счетчик обнулился – объект удаляется

Slide 46

Copy-on-write в действии Subject Proxy Proxy Client Proxy Copy 1 2 Modify Subject Subject 1 Modify

Slide 47

Пример использования Текстовый документ может содержать большое количество иллюстраций Загрузка и распаковка всех иллюстраций может занимать длительное время После открытия документа пользователь видит всего одну-две страницы Все иллюстрации документа ему сразу не видны – имеет смысл начинать загрузку иллюстрации только при необходимости ее нарисовать Важно сохранить форматирование документа даже при незагруженных изображениях Решение – временно подставлять в документ объект-заместитель вместо реального изображения Заместитель должен знать ширину и высоту изображения – они нужны для форматирования документа

Slide 48

Структура if (image == NULL) image = new CImage(fileName); image->Draw() image CDocumentEditor if (image == NULL) return extent; else return image->GetExtent()

Slide 49

Исходный код 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; };

Slide 50

Пример использования 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: В лекции рассказывается о паттернах проектирования "Компоновщик", "Декоратор", "Заместитель"

Tags: ооп паттерны проектирования c++ компоновщик composite декоратор decorator заместитель proxy

URL: