Конспект лекций по курсу "Шаблоны разработки ПО"

45
Конспект лекций по курсу «Design patterns» Немчинский Сергей, Luxoft Ukraine 2008 Программа курса История создания Что такое шаблоны проектирования? Немного про ООП Шаблоны GRASP Шаблоны GoF Немного о рефакторинге Лабораторная работа История создания В 70-х годах двадцатого века архитектор Кристофер Александр (Christopher Alexander) составил набор шаблонов проектирования. В области архитектуры эта идея не получила такого развития, как позже в области программной разработки. В 1987 году Кент Бэк (Kent Beck) и Вард Каннигем (Ward Cunningham) взяли идеи Кристофер Александра и разработали шаблоны применительно к разработке программного обеспечения для разработки графических оболочек на языке Smalltalk. В 1988 году Эрих Гамма (Erich Gamma) начал писать докторскую работу при цюрихском университете об общей переносимости этой методики на разработку программ. В 1989—1991 годах Джеймс Коплин (James Coplien) трудился над разработкой идиом для программирования на C++ и опубликовал в 1991 году книгу Advanced C++ Idioms. В этом же году Эрих Гамма заканчивает свою докторскую работу и переезжает в США, где в сотрудничестве с Ричардом Хелмом (Richard Helm), Ральфом Джонсоном (Ralph Johnson) и Джоном Влиссидсом (John Vlissides) публикует книгу Design Patterns — Elements of Reusable Object-Oriented Software. В этой книге описаны 23 шаблона проектирования. Также команда авторов этой книги известна общественности под названием «Банда 1

description

Конспект по курсу лекций. Сами лекции можно посмотреть на моем канале на YouTube: http://www.youtube.com/channel/UCVbz7l0COUdLupcY4YtYH0w

Transcript of Конспект лекций по курсу "Шаблоны разработки ПО"

Page 1: Конспект лекций по курсу "Шаблоны разработки ПО"

Конспект лекций по курсу «Design patterns»

Немчинский Сергей, Luxoft Ukraine 2008

Программа курса История создания Что такое шаблоны проектирования? Немного про ООП Шаблоны GRASP Шаблоны GoF Немного о рефакторинге Лабораторная работа

История созданияВ 70-х годах двадцатого века архитектор Кристофер Александр (Christopher Alexander) составил набор шаблонов проектирования. В области архитектуры эта идея не получила такого развития, как позже в области программной разработки.

В 1987 году Кент Бэк (Kent Beck) и Вард Каннигем (Ward Cunningham) взяли идеи Кристофер Александра и разработали шаблоны применительно к разработке программного обеспечения для разработки графических оболочек на языке Smalltalk.

В 1988 году Эрих Гамма (Erich Gamma) начал писать докторскую работу при цюрихском университете об общей переносимости этой методики на разработку программ.

В 1989—1991 годах Джеймс Коплин (James Coplien) трудился над разработкой идиом для программирования на C++ и опубликовал в 1991 году книгу Advanced C++ Idioms.

В этом же году Эрих Гамма заканчивает свою докторскую работу и переезжает в США, где в сотрудничестве с Ричардом Хелмом (Richard Helm), Ральфом Джонсоном (Ralph Johnson) и Джоном Влиссидсом (John Vlissides) публикует книгу Design Patterns — Elements of Reusable Object-Oriented Software. В этой книге описаны 23 шаблона проектирования. Также команда авторов этой книги известна общественности под названием «Банда четырёх» (англ. Gang of Four, часто сокращается до «GoF»). Именно эта книга стала причиной роста популярности шаблонов проектирования.

Таксономия паттернов Idiom

o Напрямую связана с языком программирования Specific design

o Решение частной задачи Standard design

o Дополнительный уровень абстракции Design pattern

1

Page 2: Конспект лекций по курсу "Шаблоны разработки ПО"

o Объектно-ориентированные шаблоны – отношения, взаимодействие и распределение ответственности между классами или объектами для всего класса задач

Что такое шаблоны проектирования?"Каждый паттерн описывает некую повторяющуюся проблему и ключ к ее разгадке, причем таким образом, что этим ключом можно пользоваться при решении самых разнообразных задач". Christopher Alexander

Шаблоны проектирования (паттерн, pattern) — это эффективные способы решения характерных задач проектирования, в частности проектирования компьютерных программ. Паттерн не является законченным образцом проекта, который может быть прямо преобразован в код, скорее это описание или образец для того, как решить задачу, таким образом, чтобы это можно было использовать в различных ситуациях.

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

программирования

Недостатки шаблоны могут консервировать громоздкую и малоэффективную систему понятий,

разработанную узкой группой Когда количество шаблонов возрастает, превышая критическую сложность,

исполнители начинают игнорировать шаблоны и всю систему, с ними связанную Есть мнение, что слепое применение шаблонов из справочника, замедляет

профессиональный рост программиста, так как подменяет творческую работу механическим подставлением шаблонов.

Итоги Шаблоны проектирования (паттерн, pattern) — это эффективные способы решения

характерных задач проектирования; Шаблоны - не являются законченным образцом проекта, они лишь способ решения,

«повод подумать»; Шаблоны - не панацея, но дают возможность сильно повысить свой уровень

разработчика, использовать лучший опыт; Шаблоны – ступенька к становлению Computer Science как науки, а не

ремесленечества.

ООП (Объектно-Ориентированное Программирование) парадигма программирования, в которой основной концепцией является понятие

объекта, отождествляя его с объектом предметной области.

Основные концепции Система состоит из объектов

2

Page 3: Конспект лекций по курсу "Шаблоны разработки ПО"

Объекты некоторым образом взаимодействуют между собой Каждый объект характеризуется своим состоянием и поведением

Принципы ООП Инкапсуляция Наследование Полиморфизм

Инкапсуляция это принцип, согласно которому любой класс и в более широком смысле – любая

часть системы, должны рассматриваться как чёрный ящик — пользователь класса или подсистемы должен видеть и использовать только интерфейс (т. е. список декларируемых свойств и методов) и не вникать во внутреннюю реализацию.

позволяет (теоретически) минимизировать число связей между классами и подсистемами и, соответственно, упростить независимую реализацию и модификацию классов и подсистем.

Наследование возможность порождать один класс от другого с сохранением всех свойств и

методов класса-предка (иногда его называют суперклассом) добавляя, при необходимости, новые свойства и методы.

призвано отобразить такое свойство реального мира, как иерархичность.

Полиморфизм классы-потомки могут изменять реализацию метода класса-предка, сохраняя его

сигнатуру (таким образом, сохраняя неизменным интерфейс класса-предка). позволяет обрабатывать объекты классов-потомков как однотипные объекты, не

смотря на то, что реализация методов у них может различаться.

UML UML (сокр. от англ. Unified Modeling Language — унифицированный язык

моделирования) — язык графического описания для объектного моделирования в области разработки программного обеспечения.

UML является языком широкого профиля, это открытый стандарт, использующий графические обозначения для создания абстрактной модели системы, называемой UML моделью.

3

Page 4: Конспект лекций по курсу "Шаблоны разработки ПО"

Виды диаграмм UML

Structure Diagrams: Class diagram Component diagram Composite structure diagram Collaboration (UML2.0) Deployment diagram Object diagram Package diagram

Behavior Diagrams: Activity diagram State Machine diagram Use case diagram Interaction Diagrams: Communication diagram

(UML2.0) / Collaboration (UML1.x) Interaction overview diagram

(UML2.0) Sequence diagram Timing diagram (UML2.0)

Структурные диаграммы: Классов Компонентов Композитной/составной

структуры Кооперации (UML2.0) Развёртывания Объектов Пакетов

Диаграммы поведения: Деятельности Конечных автоматов

(состояний) Прецедентов Диаграммы взаимодействия: Коммуникации (UML2.0) / Кооперации (UML1.x) Обзора взаимодействия

(UML2.0) Последовательности Синхронизации (UML2.0)

Диаграмма классов UMLДиаграмма классов, Class Diagram — статическая структурная диаграмма, описывающая структуру системы, она демонстрирует классы системы, их атрибуты и зависимости между классами.

Взаимосвязи Ассоциация

o Ассоциация представляет семейство связей двух или более классов.o Существует пять различных типов ассоциации. Наиболее же

распространёнными являются двунаправленная и однонаправленная. Например, классы рейс и самолёт связаны двунаправленной ассоциацией, а классы человек и кофейный автомат связаны однонаправленной.

Агрегацияo Агрегация — «has a» вариант ассоциации. o встречается, когда один класс является коллекцией или контейнером

других.

4

Page 5: Конспект лекций по курсу "Шаблоны разработки ПО"

o время существования содержащихся классов не зависит от времени существования содержащего их класса. Если контейнер будет уничтожен, то его содержимое — нет.

Композицияo Композиция — более строгий вариант «has a» ассоциации (отношение –

«состоит из»)o Композиция имеет жёсткую зависимость времени существования

экземпляров класса контейнера и экземпляров содержащихся классов. Если контейнер будет уничтожен, то всё его содержимое будет уничтожено также.

o Графически представляется как и агрегация, но с закрашенным ромбиком. Генерализация (обобщение)

o Генерализация показывает, что один из двух связанных классов (подтип) является более частной формой другого (надтипа), который называется обобщением первого.

o Графически генерализация представляется линией с пустым треугольником у супертипа

o Генерализация также известна как наследование или «is a» взаимосвязь.

Реализацияo Реализация — отношение между двумя элементами модели, в котором один

элемент (клиент) реализует поведение, заданное другим (поставщиком).o Графически реализация представляется также как и генерализация, но с

пунктирной линией.

5

Page 6: Конспект лекций по курсу "Шаблоны разработки ПО"

GRASP Craig Larman Книга Applying UML and Patterns, 3ed. GRASP stands for General Responsibility Assignment Software Patterns.

Полный список шаблонов GRASP Information Expert Creator Controller Low Coupling High Cohesion Polymorphism Pure Fabrication Indirection Protected Variations

6

Page 7: Конспект лекций по курсу "Шаблоны разработки ПО"

Шаблон информационный эксперт (Information Expert)- GRASP Проблема

o В системе должна аккумулироваться, рассчитываться и т. п. необходимая информация.

Решениеo Назначить обязанность аккумуляции информации, расчета и т. п. некоему

классу (информационному эксперту), обладающему необходимой информацией.

Рекомендацииo Информационным экспертом может быть не один класс, а несколько.

Примерo Необходимо рассчитать общую сумму продажи. Имеются классы

проектирования "Продажа", "ТоварПродажа" (продажа отдельного вида товара в рамках продажи в целом), "ТоварСпецификация" (описание конкретного вида товара).

o Необходимо распределить обязанности по предоставлению информации и расчету между этими классами.

o Таким образом, все три объекта являются информационными экспертами.

Создатель экземпляров класса (Creator) – GRASP Проблема

o "Кто" должен отвечать за создание экземпляров класса. Решение

o Назначить классу В обязанность создавать объекты другого класса А Рекомендации

o Логично использовать паттерн если класс В содержит, агрегирует, активно использует и т.п. объекты класса А.

Примерo необходимо определить, какой объект должен отвечать за создание

экземпляра "ТоварПродажа".

7

Page 8: Конспект лекций по курсу "Шаблоны разработки ПО"

o Логично, чтобы это был объект "Продажа", поскольку он содержит (агрегирует) несколько обьектов "ТоварПродажа".

Преимуществаo Использование этого паттерна не повышает связанности, поскольку

созданный класс, как правило, виден только для класса - создателя. Недостатки

o Если процедура создания объекта достаточно сложная (например выполняется на основе некоего внешнего условия), логично использовать паттерн "Абстрактная Фабрика",, то есть, делегировать обязанность создания объектов специальному классу.

Контроллер (Controller) – GRASP Проблема

o "Кто" должен отвечать за обработку входных системных событий? Решение

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

Рекомендацииo Для различных прецедентов логично использовать разные контроллеры

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

Преимуществаo Удобно накапливать информацию о системных событиях (в случае, если

системные операции выполняются в некоторой определенной последовательности).

o Улучшаются условия для повторного использования компонентов (системные события обрабатываются Контроллером а не элементами интерфейса пользователя).

Недостаткиo Контроллер может оказаться перегружен.

8

Page 9: Конспект лекций по курсу "Шаблоны разработки ПО"

Низкая связанность (Low Coupling) Проблема

o Обеспечить низкую связанность при создании экземпляра класса и связывании его с другим классом.

Решениеo Распределить обязанности между объектами так, чтобы степень связанности

оставалась низкой.

Высокое зацепление (High Cohesion) – GRASP Проблема

o Необходимо обеспечить выполнение объектами разнородных функций. Решение

o Обеспечить распределение обязанностей с высоким зацеплением. Преимущества

o Классы с высокой степенью зацепления просты в поддержке и повторном использовании.

Недостаткиo Иногда бывает неоправданно использовать высокое зацепление для

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

Полиморфизм (Polymorphism) – GRASP Проблема

o Как обрабатывать альтернативные варианты поведения на основе типа? o Как заменять подключаемые компоненты системы?

9

Page 10: Конспект лекций по курсу "Шаблоны разработки ПО"

Решениеo Обязанности распределяются для различных вариантов поведения с

помощью полиморфных операций для этого класса. o Каждая внешняя система имеет свой интерфейс.

Преимуществаo Впоследствии легко расширять и модернизировать систему.

Недостаткиo Не следует злоупотреблять добавлением интерфейсов с применением

принципа полиморфизма с целью обеспечения дееспособности системы в неизвестных заранее новых ситуациях.

Искусственный (Pure Fabrication) – GRASP Проблема

o Какой класс должен обеспечивать реализацию паттернов "Высокое зацепление", и "Низкая связанность"?

Решениеo Присвоить группу обязанностей с высокой степенью зацепления классу,

который не представляет конкретного понятия из предметной области (синтезировать искусственную сущность для обеспечения высокого зацепления и слабого связывания).

Примерo Какой класс должен сохранять экземпляры класса "Продажа" в реляционной

базе данных? o Если возложить эту обязанность на класс "Продажа", то будем иметь

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

o Хранение объектов в реляционной базе данных - это общая задача, которую придется решать для многих классов.

o Решением данной проблемы будет создание нового класса "ПостоянноеХранилище", ответственного за сохранение обьектов некоторого вида в базе данных.

10

Page 11: Конспект лекций по курсу "Шаблоны разработки ПО"

Преимуществаo Класс "ПостоянноеХранилище" будет обладать низкой степенью

связывания и высокой степенью зацепления. Недостатки

o Данным паттерном не следует злоупотреблять иначе все функции системы превратятся в объекты.

Перенаправление (Indirection) – GRASP Проблема

o Как перераспределить обязанности обьектов, чтобы обеспечить отсутствие прямого связывания?

Решениеo Присвоить обязанности по обеспечению связи между службами или

компонентами промежуточному объекту. Пример

o См. пример к паттерну "Искусственный". Класс "Хранилище" выступает в роли промежуточного звена между классом "Продажа" и базой данных.

Устойчивый к изменениям (Protected Variations) – GRASP Проблема

o Как спроектировать систему так, чтобы изменение одних ее элементов не влияло на другие?

Решениеo Идентифицировать точки возможных изменений или неустойчивости и

распределить обязанности таким образом, чтобы обеспечить устойчивую работу системы.

11

Page 12: Конспект лекций по курсу "Шаблоны разработки ПО"

Анти-паттерныАнти-паттерны (anti-patterns), также известные как ловушки (pitfalls) — это классы наиболее часто внедряемых плохих решений проблем. Они изучаются, как категория, в случае когда их хотят избежать в будущем, и некоторые отдельные случаи их могут быть распознаны при изучении неработающих систем.

Анти-паттерны в объектно-ориентированном программировании

Базовый класс-утилита (BaseBean): Наследование функциональности из класса-утилиты вместо делегирования к нему

Вызов предка (CallSuper): Для реализации прикладной функциональности методу класса-потомка требуется в обязательном порядке вызывать те же методы класса-предка.

Ошибка пустого подкласса (Empty subclass failure): Создание класса, который не проходит «проверку пустоты подкласса» («Empty Subclass Test») из-за различного поведения по сравнению с классом, который наследуется от него без изменений

Божественный объект (God object): Концентрация слишком большого количества функций в одиночной части дизайна (классе)

Объектная клоака (Object cesspool): Переиспользование объектов, чьё состояние не удовлетворяет (возможно неявному) контракту переиспользования.

Полтергейст (компьютер) (Poltergeist): Объекты, чьё единственное предназначение — передавать информацию другим объектам

Проблема йо-йо (Yo-yo problem): Структура (например: наследования) которая тяжело понятна вследствие избыточной фрагментации

Синглетонизм (Singletonitis): Избыточное использование паттерна синглетон

Анти-паттерны в программировании Ненужная сложность (Accidental complexity): Внесение ненужной сложности в

решение Действие на расстоянии (Action at a distance): Неожиданное взаимодействие между

широко разделёнными частями системы Накопить и запустить (Accumulate and fire): Установка параметров подпрограмм в

наборе глобальных переменных Слепая вера (Blind faith): Недостаточная проверка (a) корректности исправления

ошибки или (b) результата работы подпрограммы Лодочный якорь (Boat anchor): Сохранение более не используемой части системы Активное ожидание (Busy spin): Потребление ресурсов ЦПУ (процессорного

времени) во время ожидания события, обычно при помощи постоянно повторяемой проверки, вместо того, чтобы использовать систему сообщений

Кэшированный сбой (Caching failure): Забывать сбросить флаг ошибки после её обработки

Проверка типа вместо интерфейса (Checking type instead of membership, Checking type instead of interface): Проверка того, что объект имеет специфический тип в то время, когда требуется только определённый интерфейс

Инерция кода (Code momentum): Сверхограничение части системы путём постоянного подразумевания её поведения в других частях системы

Кодирование путём исключения (Coding by exception): Добавление нового кода для поддержки каждого специального распознанного случая

Таинственный код (Cryptic code): Использование аббревиатур вместо полных (самоописывающих) имён

12

Page 13: Конспект лекций по курсу "Шаблоны разработки ПО"

Блокировка с двойной проверкой (Double-checked locking): Проверка перед блокировкой может выйти из строя в случае использования современного аппаратного обеспечения или компиляторов

Жёсткое кодирование (Hard code): Внедрение предположений об окружении системы в слишком большом количестве точек её реализации

Магические числа (Magic numbers): Включение чисел в алгоритмы без объяснений Процедурный код (Procedural code): Когда другая парадигма является более

подходящей Спагетти-код (Spaghetti code): Системы, чья структура редко понятна, особенно

потому что структура кода используется неправильно Мыльный пузырь (Soap bubble): Класс, инициализированый мусором, максимально

долго притворяется, что содержит какие-то данные.

Методологические анти-паттерны Программирование методом копирования-вставки (Copy and paste programming):

Копирование (и лёгкая модификация) существующего кода вместо создания общих решений

Дефакторинг (De-Factoring): Процесс уничтожения функциональности и замены её документацией

Золотой молот (Golden hammer): Сильная уверенность в том, что любимое решение универсально применимо

Фактор невероятности (Improbability factor): Предположение о невозможности того, что сработает известная ошибка

Преждевременная оптимизация (Premature optimization): Оптимизация на основе недостаточной информации

Изобретение колеса (Reinventing the wheel): Ошибка адаптации существующего решения

Изобретение квадратного колеса (Reinventing the square wheel): Создание плохого решения, когда существует хорошее

Шаблоны GoF

Типы шаблонов Порождающие паттерны проектирования Структурные паттерны проектирования классов/объектов Паттерны проектирования поведения классов/объектов

Порождающие паттерны (Creational Patterns) Абстрактная фабрика (Abstract Factory, Factory), др. название Инструментарий (Kit) Одиночка (Singleton) Прототип (Prototype) Строитель (Builder) Фабричный метод (Factory Method) или Виртуальный конструктор (Virtual

Constructor)

13

Page 14: Конспект лекций по курсу "Шаблоны разработки ПО"

Абстрактная фабрика (Abstract Factory, Factory) Проблема

o Создать семейство взаимосвязанных или взаимозависимых объектов (не специфицируя их конкретных классов).

Решениеo Создать абстрактный класс, в котором объявлен интерфейс для создания

конкретных классов.

/* * GUIFactory example */#include <iostream>#include <string>#include <cstdlib>using namespace std; class Button{public: virtual ~Button() { } virtual void paint() = 0;}; class WinButton : public Button{public: void paint()

14

Page 15: Конспект лекций по курсу "Шаблоны разработки ПО"

{ cout<<"I'm a WinButton: "; }}; class OSXButton : public Button{public: void paint() { cout<<"I'm an OSXButton: "; }}; class GUIFactory {public: virtual ~GUIFactory() { } virtual Button* createButton() = 0; static GUIFactory* getFactory();}; class WinFactory : public GUIFactory{public: Button* createButton() { return new WinButton(); }}; class OSXFactory : public GUIFactory{public: Button* createButton() { return new OSXButton(); }}; int main(){ GUIFactory* factory = GUIFactory::getFactory(); Button* button = factory->createButton(); button->paint(); delete factory; delete button; return 0;}// Output is either:// "I'm a WinButton:"

15

Page 16: Конспект лекций по курсу "Шаблоны разработки ПО"

// or:// "I'm an OSXButton:"

Преимуществаo Изолирует конкретные классы. Поскольку "Абстрактная фабрика"

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

o Упрощена замена "Абстрактной фабрики", поскольку она используется в приложении только один раз при инстанцировании.

Недостаткиo Интерфейс "Абстрактной фабрики" фиксирует набор объектов, которые

можно создать. Расширение "Абстрактной фабрики" для изготовления новых объектов часто затруднительно.

Фабричный метод (Factory Method ) или Виртуальный конструктор (Virtual Constructor)

Проблемаo Определить интерфейс для создания объекта, но оставить подклассам

решение о том, какой класс инстанцировать, то есть, делегировать инстанцирование подклассам.

Используется, когда:o классу заранее неизвестно, объекты каких подклассов ему нужно создавать.o класс спроектирован так, чтобы объекты, которые он создаёт,

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

подклассов, и планируется локализовать знание о том, какой класс принимает эти обязанности на себя.

Решениеo Абстрактный класс "Создатель" объявляет ФабричныйМетод,

возвращающий объект типа "Продукт" (абстрактный класс, определяющий интерфейс обьектов, создаваемых фабричным методом).

o "Создатель также может определить реализацию по умолчанию ФабричногоМетода, который возвращает "КонкретныйПродукт". "КонкретныйСоздатель" замещает ФабричныйМетод, возвращающий объект "КонкретныйПродукт".

o "Создатель" "полагается" на свои подклассы в определении ФабричногоМетода, возвращающего объект "КонкретныйПродукт"

16

Page 17: Конспект лекций по курсу "Шаблоны разработки ПО"

class Computer { public: virtual void Run() = 0; virtual void Stop() = 0; }; class Laptop: public Computer { public: virtual void Run(){mHibernating = false;} virtual void Stop(){mHibernating = true;} private: bool mHibernating; // Whether or not the machine is hibernating }; class Desktop: public Computer { public: virtual void Run(){mOn = true;} virtual void Stop(){mOn = false;} private: bool mOn; // Whether or not the machine has been turned on };

class ComputerFactory { public: static Computer *NewComputer(const std::string &description) { if(description == "laptop") return new Laptop; if(description == "desktop") return new Desktop; return NULL; } };

17

Page 18: Конспект лекций по курсу "Шаблоны разработки ПО"

Преимуществаo Избавляет проектировщика от необходимости встраивать в код зависящие

от приложения классы. Недостатки

o Возникает дополнительный уровень подклассов.

18

Page 19: Конспект лекций по курсу "Шаблоны разработки ПО"

Структурные паттерны Адаптер (Adapter) Декоратор (Decorator) или Оболочка (Wrapper) Заместитель (Proxy) или Суррогат (Surrogate) Компоновщик (Composite) Мост (Bridge), Handle (описатель) или Тело (Body) Приспособленец (Flyweight) Фасад (Facade)

Адаптер (Adapter) Проблема

o Необходимо обеспечить взаимодействие несовместимых интерфейсов или как создать единый устойчивый интерфейс для нескольких компонентов с разными интерфейсами.

Решениеo Конвертировать исходный интерфейс компонента к другому виду с

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

// Purpose. Adapter design pattern demo// // Discussion. LegacyRectangle's interface is not compatible with the// system that would like to reuse it. An abstract base class is created// that specifies the desired interface. An "adapter" class is defined// that publicly inherits the interface of the abstract class, and// privately inherits the implementation of the legacy component. This

19

Page 20: Конспект лекций по курсу "Шаблоны разработки ПО"

// adapter class "maps" or "impedance matches" the new interface to the// old implementation.

#include <iostream.h>

typedef int Coordinate;typedef int Dimension;

/////////////////////////// Desired interface ///////////////////////////class Rectangle {public: virtual void draw() = 0;};

/////////////////////////// Legacy component ///////////////////////////class LegacyRectangle {public: LegacyRectangle( Coordinate x1, Coordinate y1, Coordinate x2, Coordinate y2 ) { x1_ = x1; y1_ = y1; x2_ = x2; y2_ = y2; cout << "LegacyRectangle: create. (" << x1_ << "," << y1_ << ") => (" << x2_ << "," << y2_ << ")" << endl; } void oldDraw() { cout << "LegacyRectangle: oldDraw. (" << x1_ << "," << y1_ << ") => (" << x2_ << "," << y2_ << ")" << endl; }private: Coordinate x1_; Coordinate y1_; Coordinate x2_; Coordinate y2_;};

/////////////////////////// Adapter wrapper ///////////////////////////class RectangleAdapter : public Rectangle, private LegacyRectangle {public: RectangleAdapter( Coordinate x, Coordinate y, Dimension w, Dimension h ) : LegacyRectangle( x, y, x+w, y+h ) { cout << "RectangleAdapter: create. (" << x << "," << y << "), width = " << w << ", height = " << h << endl; } virtual void draw() { cout << "RectangleAdapter: draw." << endl; oldDraw(); }};

20

Page 21: Конспект лекций по курсу "Шаблоны разработки ПО"

void main() { Rectangle* r = new RectangleAdapter( 120, 200, 60, 40 ); r->draw();}

// LegacyRectangle: create. (120,200) => (180,240)// RectangleAdapter: create. (120,200), width = 60, height = 40// RectangleAdapter: draw.// LegacyRectangle: oldDraw. (120,200) => (180,240)

Применяется в случаяхo система поддерживает требуемые данные и поведение, но имеет

неподходящий интерфейс. Плюсы

o инкапсуляция реализации внешних классов (компонентов, библиотек), система становится независимой от интерфейса внешних классов;

o переход на использование других внешних классов не требует переделки самой системы, достаточно реализовать один класс Adapter.

Декоратор (Decorator) или Оболочка (Wrapper) Проблема

o Возложить дополнительные обязанности (прозрачные для клиентов) на отдельный объект, а не на класс в целом.

Рекомендацииo Применение нескольких "Декораторов" к одному "Компоненту" позволяет

произвольным образом сочетать обязанности, например, одно свойство можно добавить дважды.

Решениеo Динамически добавить объекту новые обязанности не прибегая при этом к

порождению подклассов (наследованию). o "Компонент"определяет интерфейс для обьектов, на которые могут быть

динамически возложены дополнительные обязанности, "КонкретныйКомпонент" определяет объект, на который возлагаются дополнительные обязанности, "Декоратор" - хранит ссылку на объект "Компонент" и определяет интерфейс, соответствующий интерфейсу "Компонента". "КонкретныйДекоратор" возлагает дополнительные обязанности на компонент.

o "Декоратор" переадресует запросы объекту "Компонент".

21

Page 22: Конспект лекций по курсу "Шаблоны разработки ПО"

#include <iostream> using namespace std; /* Component (interface) */class Widget { public: virtual void draw() = 0; }; /* ConcreteComponent */class TextField : public Widget { private: int width, height; public: TextField( int w, int h ){ width = w; height = h; } void draw() { cout << "TextField: " << width << ", " << height << '\n'; }};

/* Decoder (interface)*/ class Decorator : public Widget {

22

Page 23: Конспект лекций по курсу "Шаблоны разработки ПО"

private: Widget* wid; // reference to Widget public: Decorator( Widget* w ) { wid = w; } void draw() { wid->draw(); }}; /* ConcreteDecoderA */class BorderDecorator : public Decorator { public: BorderDecorator( Widget* w ) : Decorator( w ) { } void draw() { Decorator::draw(); cout << " BorderDecorator" << '\n'; } };

/* ConcreteDecoderB */class ScrollDecorator : public Decorator { public: ScrollDecorator( Widget* w ) : Decorator( w ) { } void draw() { Decorator::draw(); cout << " ScrollDecorator" << '\n'; } }; void main( void ) { Widget* aWidget = new BorderDecorator( new BorderDecorator( new ScrollDecorator( new TextField( 80, 24 )))); aWidget->draw();}

Преимуществаo Большая гибкость, чем у статического наследования: можно добавлять и

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

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

23

Page 24: Конспект лекций по курсу "Шаблоны разработки ПО"

Недостаткиo "Декоратор" и его "Компонент" не идентичны, и, кроме того, получается,

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

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

o Как обрабатывать группу или композицию структур объектов одновременно?

Решениеo Определить классы для композитных и атомарных объектов таким образом,

чтобы они реализовывали один и тот же интерфейс.

// file graphic.h starting here #include <iostream.h>class Composite;class Graphic {friend class Composite; Graphic *next; virtual int pictureDetect(){return(O);}public: virtual void draw(){} Graphic(){next=NULL;}};class Line : public Graphic { int xl,yl,x2,y2; public: virtual void draw(void){cout<<"Line:"<<xl<<yl<<x2<<y2<<'\n';) Line(int Xl,int Yl,int X2,int Y2){xl=X1; yl=Y1; x2=X2; y2=Y2;}

24

Page 25: Конспект лекций по курсу "Шаблоны разработки ПО"

};class Text : public Graphic { int x,y; char *text;public: virtual void draw(void){cout<<"Text:g <<X<<y<< text<<'\n';} Text(int X,int Y. char *tx){x=X; y=Y; text=tx;}};class Picture : public Graphic {friend class Composite; Graphic *first; virtual int pictureDetect(){return(1);}public: virtual void draw(void){cout<<"Picture:\nN;} Picture(){first=NULL;}

class Composite {public: void add(Picture *p, Graphic *g){g->next=p->first; p->first=g;} void remove(Picture *p, Graphic *g); void draw(Graphic *g); void dissolve(Graphic *g);};void Composite::remove(Picture *p, Graphic *g){ Graphic *t; if(p->first==g){p->first=g->next; g->next=NULL;} else { for(t=p->first; t; t=t->next) if(t->next==g)break; if(t){t->next=g->next; g->next=NULL;} else cout<<"error\n"; }}

void Composite::draw(Graphic *g){ static int level=O; // not necessary, just for better // displays int i; Graphic *t;

for(i=O;ilevel;i++)cout<<" "; // just to indent the

// print g->draw(); if(g->pictureDetect()){ level++; for(t=((Picture *)g)->first; t; t=t->next) draw(t); level-; }}

25

Page 26: Конспект лекций по курсу "Шаблоны разработки ПО"

void Composite::dissolve(Graphic *g){ Graphic *t,*tn;

if(g->pictureDetect(){ for(t=((Picture *)g)->first; t; t=tn){ tn=t->next; t->next=NULL; } ((Picture *)g)->first=NULL; }}

int main(void){ Line *nl,*n2,*n3,*n4; Text *tl,*t2,*t3 Picture *pl,*p2,*p3; nl=new Line(1,1,11,11); n2=new Line(2,2,22,22); n3=new Line(3,3,33,33); n4=new Line(4,4,44,44); tl=new Text(l,l,"one"); t2=new Text(2,2,"two"); t3=new Text(3,3,"three"); pl=new Picture; p2=new Picture; p3=new Picture; Composite comp;

comp.add(pl,nl); comp.add(pl,n2); comp.add(p2,n3); comp.add(p2,n4); comp.add(p3,tl); comp.add(p3,t2); comp.add(p2,t3); comp.add(p2,pl); comp.add(p3,p2); comp.remove(p2,n4); comp.add(p3,n4);

comp.draw(p3); comp.dissolve(p2); cout<<'\n'; comp.draw(p3); return(O);}

Мост (Bridge), Handle (описатель) или Тело (Body) Проблема

o Требуется отделить абстракцию от реализации так, чтобы и то и другое можно было изменять независимо. При использовании наследования реализация жестко привязывается к абстракции, что затрудняет независимую модификацию.

Решениеo Поместить абстракцию и реализацию в отдельные иерархии классов.

26

Page 27: Конспект лекций по курсу "Шаблоны разработки ПО"

using namespace std; /********************************************//* Implementor *//********************************************/// interfaceclass DrawingAPI {public: virtual void drawCircle(double x, double y, double radius) = 0;}; //concrete implementor1class DrawingAPI1 : public DrawingAPI{public: void drawCircle(double x, double y, double radius) { cout<<"API1.circle at"<<x<<":"<<y<<" "<< radius<<endl; }}; //concrete implementor2class DrawingAPI2 : public DrawingAPI{public: void drawCircle(double x, double y, double radius) { cout<<"API2.circle at "<<x<<":"<<y<<" "<< radius<<endl; }};

27

Page 28: Конспект лекций по курсу "Шаблоны разработки ПО"

/**************************************//* Abstraction *//**************************************/class Shape{public: virtual void draw() = 0; virtual void resizeByPercentage(double pct) = 0;}; class CircleShape:public Shape{public: CircleShape(double x, double y,double radius,DrawingAPI &drawingAPI): m_x(x),m_y(y),m_radius(radius),m_drawingAPI(drawingAPI) {} void draw() { m_drawingAPI.drawCircle(m_x,m_y,m_radius); } void resizeByPercentage(double pct) { m_radius *= pct; }private: double m_x,m_y,m_radius; DrawingAPI& m_drawingAPI;};

//////////////////////////////////////////Testtypedef std::vector<Shape*>::iterator ShapeIt;int main(int argc, char* argv[]){ std::vector<Shape*> vecShapes; vecShapes.push_back(new CircleShape(1,2,3,*(new DrawingAPI1))); vecShapes.push_back(new CircleShape(5,7,11,*(new DrawingAPI2))); ShapeIt begin,end; begin = vecShapes.begin(); end = vecShapes.end(); for_each(begin,end,std::bind2nd(std::mem_fun(&Shape::resizeByPercentage),2.5)); for_each(begin,end,mem_fun(&Shape::draw)); return 0;}

Преимуществаo Отделение реализации от интерфейса, то есть, "Реализацию" "Абстракции"

можно конфигурировать во время выполнения. o Разделение классов "Абстракция" и "Реализация" устраняет зависимости от

реализации, устанавливаемые на этапе компиляции: чтобы изменить класс "Реализация" вовсе не обязательно перекомпилировать класс "Абстракция".

28

Page 29: Конспект лекций по курсу "Шаблоны разработки ПО"

Фасад (Facade) Проблема

o Как обеспечить унифицированный интерфейс с набором разрозненных реализаций или интерфейсов, например, с подсистемой, если нежелательно высокое связывание с этой подсистемой или реализация подсистемы может измениться?

Решениеo Определить одну точку взаимодействия с подсистемой - фасадный объект,

обеспечивающий общий интерфейс с подсистемой и возложить на него обязанность по взаимодействию с ее компонентами.

o Фасад - это внешний объект, обеспечивающий единственную точку входа для служб подсистемы.

o Реализация других компонентов подсистемы закрыта и не видна внешним компонентам.

o Фасадный объект обеспечивает реализацию паттерна «Устойчивый к изменениям» (Protected Variations) с точки зрения защиты от изменений в реализации подсистемы.

Поведенческие шаблоны (Behavioral) Интерпретатор (Interpreter) Итератор (Iterator) или Курсор (Cursor) Команда (Command), Действие (Action) или Транзакция (Транзакция) Наблюдатель (Observer), Опубликовать - подписаться (Publish - Subscribe) или

Delegation Event Model Посетитель (Visitor) Посредник (Mediator) Состояние (State)

29

Page 30: Конспект лекций по курсу "Шаблоны разработки ПО"

Стратегия (Strategy) Хранитель (Memento) Цепочка обязанностей (Chain of Responsibility) Шаблонный метод (Template Method)

Итератор (Iterator) или Курсор (Cursor) Проблема

o Составной объект, например, список, должен предоставлять доступ к своим элементам (объектам), не раскрывая их внутреннюю структуру, причем перебирать список требуется по-разному в зависимости от задачи.

Решениеo Создается класс "Итератор", который определяет интерфейс для доступа и

перебора элементов, "КонкретныйИтератор" реализует интерфейс класса "Итератор" и следит за текущей позицией при обходе "Агрегата".

o "Агрегат" определяет интерфейс для создания объекта - итератора. o "КонкретныйАгрегат" реализует интерфейс создания итератора и

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

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

Команда (Command), Действие (Action) или Транзакция (Транзакция)

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

операции запрошено и кто будет получателем. Решение

30

Page 31: Конспект лекций по курсу "Шаблоны разработки ПО"

Инкапсулировать запрос как объект. "Клиент" создает объект "КонкретнаяКоманда", который вызывает операции

получателя для выполнения запроса, "Инициатор" отправляет запрос, выполняя операцию "Команды" Выполнить().

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

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

Преимущества Обеспечивает обработку команды в виде объекта, что позволяет сохранять

её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.

Посетитель (Visitor). Проблема

Над каждым объектом некоторой структуры выполняется операция. Определить новую операцию, не изменяя классы объектов.

Решение Клиент, использующий данный паттерн, должен создать объект класса

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

"КонкретныйЭлемент" (имя и сигнатура данной операции идентифицируют класс, элемент которого посещает "Посетитель" - то есть, посетитель может обращаться к элементу напрямую).

31

Page 32: Конспект лекций по курсу "Шаблоны разработки ПО"

"КонкретныйПосетитель" реализует все операции, обьявленные в классе "Посетитель".

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

Класс "КонкретныйПосетитель"предоставляет контекст для этого алгоритма и сохраняет его локальное состояние.

"Элемент" определяет операцию "Принять", которая принимает "Посетителя" в качестве аргумента, "КонкретныйЭлемент" реализует операцию "Принять", которая принимает "Посетителя" в качестве аргумента.

"СтруктураОбьекта" может перечислить свои аргументы и предоставить посетителю высокоуровневый интерфейс для посещения своих элементов.

Рекомендации Логично использовать, если в структуре присутствуют объекты многих

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

Преимущества Упрощается добавление новых операций, объединяет родственные

операции в классе "Посетитель". Недостатки

Затруднено добавление новых классов "КонкретныйЭлемент", поскольку требуется объявление новой абстрактной операции в классе "Посетитель".

32

Page 33: Конспект лекций по курсу "Шаблоны разработки ПО"

Состояние (State) Проблема

Варьировать поведение объекта в зависимости от его внутреннего состояния Решение

Класс "Контекст" делегирует зависящие от состояния запросы текущему объекту "КонкретноеСостояние" (хранит экземпляр подкласса "КонкретноеСостояние", которым определяется текущее состояние), и определяет интерфейс, представляющий интерес для клиентов.

"КонкретноеСостояние" реализует поведение, ассоциированное с неким состоянием объекта "Контекст". "Состояние" определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным экземпляром "Контекста".

Преимущества Локализует зависящее от состояния поведение и делит его на части,

соответствующие состояниям, переходы между состояниями становятся явными.

Стратегия (Strategy) Проблема

Спроектировать изменяемые, но надежные алгоритмы или стратегии. Решение

Определить для каждого алгоритма или стратегии отдельный класс со стандартным интерфейсом.

Пример Обеспечение сложной логики вычисления стоимости товаров с учетом

сезонных скидок, скидок постоянным клиентам и т. п. Данная стратегия может изменяться.

Создается несколько классов "Стратегия", каждый из которых содержит один и тот же полиморфный метод "ЦенаРассчитать". В качестве параметров в этот метод передаются данные о продаже. Объект стратегии связывается с контекстным объектом (тем объектом, к которому применяется алгоритм).

33

Page 34: Конспект лекций по курсу "Шаблоны разработки ПО"

Плюсы инкапсуляция реализации различных алгоритмов, система становится

независимой от возможных изменений бизнес-правил; вызов всех алгоритмов одним стандартным образом; отказ от использования переключателей и/или условных операторов.

Минусы создание дополнительных классов

Цепочка обязанностей (Chain of Responsibility) Проблема

Запрос должен быть обработан несколькими объектами. Рекомендации

Логично использовать данный паттерн, если имеется более одного объекта, способного обработать запрос и обработчик заранее неизвестен (и должен быть найден автоматически) или если весь набор объектов, которые способны обработать запрос, должен задаваться автоматически.

Шаблон рекомендован для использования в условиях: в разрабатываемой системе имеется группа объектов, которые могут

обрабатывать сообщения определенного типа; все сообщения должны быть обработаны хотя бы одним объектом системы; сообщения в системе обрабатываются по схеме «обработай сам либо

перешли другому», то есть одни сообщения обрабатываются на том уровне, где они получены, а другие пересылаются объектам иного уровня.

34

Page 35: Конспект лекций по курсу "Шаблоны разработки ПО"

Связать объекты - получатели запроса в цепочку и передать запрос вдоль этой цепочки, пока он не будет обработан.

"Обработчик" определяет интерфейс для обработки запросов, и, возможно, реализует связь с преемником,

Преимущества Ослабляется связанность (объект не обязан "знать", кто именно обработает

его запрос). Недостатки

Нет гарантий, что запрос будет обработан, поскольку он не имеет явного получателя.

Шаблонный метод (Template Method) Проблема

Определить алгоритм и реализовать возможность переопределения некоторых шагов алгоритма для подклассов (без изменения общей структуры алгоритма.

Решение "АбстрактныйКласс" определяет абстрактные Операции(), замещаемые в

конкретных подклассах для реализации шагов алгоритма, и реализует ШаблонныйМетод(), определяющий "скелет" алгоритма. "КонкретныйКласс" релизует Операции(), выполняющие шаги алгоритма способом, который зависит от подкласса. "КонкретныйКласс" предполагает, что инвариантные шаги алгоритма будут выполнены в "АбстрактномКлассе".

35

Page 36: Конспект лекций по курсу "Шаблоны разработки ПО"

Model-view-controller Model-view-controller (MVC, «Модель-представление-поведение») — архитектура

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

Шаблон MVC позволяет разделить данные, представление и обработку действий пользователя на три отдельных компонента

Модель (Model). Модель предоставляет данные (обычно для View), а также реагирует на запросы (обычно от контролера), изменяя свое состояние .

Представление (View). Отвечает за отображение информации (пользовательский интерфейс).

Поведение (Controller). Интерпретирует данные, введенные пользователем, и информирует модель и представление о необходимости соответствующей реакции.

36

Page 37: Конспект лекций по курсу "Шаблоны разработки ПО"

Важно отметить, что как представление, так и поведение зависят от модели. Однако модель не зависит ни от представления, ни от поведения. Это одно из ключевых достоинств подобного разделения. Оно позволяет строить модель независимо от визуального представления.

Впервые данный шаблон проектирования был предложен для языка Smalltalk.

Рефакторинг

Рефакторинг или Реорганизация — процесс полного или частичного преобразования внутренней структуры программы при сохранении её внешнего поведения.

В его основе лежит последовательность небольших эквивалентных (т.е., сохраняющих поведение) преобразований.

Поскольку каждое преобразование маленькое, программисту легче проследить за его правильностью, и в то же время, вся последовательность может привести к существенной перестройке программы и улучшению её согласованности и четкости.

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

Рефакторинг изначально не предназначен для исправления ошибок и добавления новой функциональности, но помогает избежать ошибок и облегчить добавление функциональности.

Наиболее употребимые методы рефакторинга: Изменение сигнатуры метода (Change Method Signature) Инкапсуляция поля (Encapsulate Field) Выделение класса (Extract Class) Выделение интерфейса (Extract Interface) Выделение локальной переменной (Extract Local Variable) Выделение метода (Extract Method) Генерализация типа (Generalize Type) Встраивание (Inline) Введение фабрики (Introduce Factory)

37

Page 38: Конспект лекций по курсу "Шаблоны разработки ПО"

Введение параметра (Introduce Parameter) Подъём поля/метода (Pull Up) Спуск поля/метода (Push Down) Замена условного оператора полиморфизмом (Replace Conditional with

Polymorphism)

38