752.32K
Категория: ОбразованиеОбразование

Шаблоны проектирования

1.

Шаблоны проектирования
Айжулов Д.
[email protected]
Satbayev University

2.

Архитектура
• - фундаментальная организация системы, воплощенная в ее компонентах,
их взаимоотношения друг с другом и с окружающей средой, а также
принципы, лежащие в основе ее проектирования и развития.
• Архитектура определяет контекст для проектирования и реализации.
• Архитектурные решения - это самые принципиальные решения; их
изменение будет иметь значительный волновой эффект.
• Шаблоны (паттерны) проектирования (design patterns) - это типичные
решения, часто возникающих проблем при разработке программного
обеспечения.
• Абстрагируются над уровнем реальных классов, экземпляров, компонентов
• Признаки хорошего проектировщика программного обеспечения:
• Умение обращаться к большой и разнообразной коллекции шаблонов, которые ему
хорошо известны
• Умение выяснить, какие шаблоны могут применяться к рассматриваемой проблеме
• Умение быстро превратить шаблон в реализацию для обработки текущей проблемы

3.

Классификация шаблонов проектирования
• Шаблоны проектирования отличаются своей сложностью, уровнем
детализации и масштабом применимости ко всей проектируемой системе.
Самые простые и низкоуровневые шаблоны часто называют идиомами.
Обычно они применяются только к одному языку программирования.
• Самыми универсальными и высокоуровневыми шаблонами являются
архитектурные шаблоны. Разработчики могут реализовать эти шаблоны
практически на любом языке. В отличие от других шаблонов, их можно
использовать для проектирования архитектуры всего приложения.
• Шаблоны можно классифицировать по их предназначению или назначению.
• Шаблоны создания (creational patterns) предоставляют механизмы создания объектов,
повышающие гибкость и многократное использование существующего кода.
• Структурные шаблоны (structural patterns) объясняют, как собирать объекты и классы в
более крупные структуры, сохраняя при этом гибкость и эффективность.
• Поведенческие шаблоны (behavioral patterns) заботятся об эффективном общении и
распределении обязанностей между объектами.

4.

Хорошая архитектура
• Одно из качеств хорошей архитектуры – контроль изменений
• Хорошее проектирование - это когда я вносятся изменения, создается
впечатление, что вся программа создавалась в ожидании этого.
• Задачи решаются с помощью всего нескольких вызовов функций, которые
идеально подходят для решения, не оставляя ни малейшей ряби на
безмятежной поверхности кода.
Производите
льность
Совместимость
Масштабируе
мость Надежност
ь/доступно
сть
Объем
Безопасно
сть
Стоимость
/сроки
Стойкость
(гибкость)
Функциона
льность
Актуальнос
ть
технологий
ПО

5.

Производительность
• Многие шаблоны, которые делают код более гибким, полагаются на виртуальную
диспетчеризацию, интерфейсы, указатели, сообщения и другие механизмы,
которые имеют хотя бы некоторую стоимость выполнения.
• Однако, во многих реализациях (например шаблоны C++) предоставляют
абстракцию интерфейсов без каких-либо «штрафов» во время выполнения
• Во многом архитектура программного обеспечения направлена ​на то, чтобы
сделать программу более гибкой. Речь идет о том, чтобы изменение потребовало
меньше усилий.
• Это означает, что в программе кодируется меньше предположений. Используете
интерфейсы, чтобы код работал с любым классом, который его реализует, а не
только с тем, который работает сегодня.
• Вы используете наблюдателей и обмен сообщениями, чтобы позволить двум
частям игры разговаривать друг с другом, так что завтра их легко может быть три
или четыре.
• Но производительность - это все о предположениях. Практика оптимизации
опирается на конкретные ограничения. Можем ли мы с уверенностью
предположить, что у нас никогда не будет больше 256 врагов? Отлично, мы можем
упаковать идентификатор в один байт. Будем ли мы здесь вызывать метод только
для одного конкретного типа? Хорошо, мы можем статически отправить или
встроить его. Все ли сущности будут одного класса? Отлично, мы можем сделать из
них хороший непрерывный массив.

6.

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

7.

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

8.

void InputHandler::handleInput()
{
if (isPressed(BUTTON_X)) jump();
else if (isPressed(BUTTON_Y)) fireGun();
else if (isPressed(BUTTON_A)) swapWeapon();
else if (isPressed(BUTTON_B)) lurchIneffecti
}
Пример
• Где-то в каждой игре есть фрагмент кода, который
считывает пользовательский ввод - нажатия кнопок,
события клавиатуры, щелчки мыши и т. д., и
переводит его в значимое действие в игре. Эта
функция обычно вызывается игровым циклом один
class Command
раз за кадр. Мы хотим, чтобы пользователь мог
{
настраивать свои кнопки.
public:
class InputHandler
{
public:
void handleInput();
private:
Command* buttonX_;
Command* buttonY_;
Command* buttonA_;
Command* buttonB_;
};
void InputHandler::handleInput()
{
if (isPressed(BUTTON_X)) buttonX_->execute();
else if (isPressed(BUTTON_Y)) buttonY_->execute();
else if (isPressed(BUTTON_A)) buttonA_->execute();
else if (isPressed(BUTTON_B)) buttonB_->execute();
}
virtual ~Command() {}
virtual void execute() = 0;
};
class JumpCommand : public Command
{
public:
virtual void execute() { jump(); }
};
class FireCommand : public Command
{
public:
virtual void execute(){ fireGun(); }
};

9.

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

10.

Поведенческий шаблон: Observer
• Проблема
позволить определить механизм подписки для уведомления нескольких
объектов о любых событиях, которые происходят с объектом, который они
наблюдают
• Решение
Разбить бизнес-логику на две части: основная функциональность,
независимая от другого кода, будет действовать как издатель; остальное
превратится в набор классов подписчиков.
Объявить интерфейс подписчика с единственным методом обновления.
Объявить интерфейс издателя и описать пару методов для добавления
объекта-подписчика в список и его удаления из списка.
Решить, где разместить фактический список подписки и реализацию методов
подписки. Обычно этот код выглядит одинаково для всех типов издателей,
поэтому очевидное место для его размещения находится в абстрактном
классе, производном непосредственно из интерфейса издателя. Конкретные
издатели расширяют этот класс, наследуя поведение подписки.
При применении шаблона к существующей иерархии классов поместить
логику подписки в отдельный объект и заставить всех реальных издателей
использовать ее.
Создать конкретные классы издателя, который должен уведомить всех своих
подписчиков каждый раз, когда внутри издателя происходит что-то важное.
Реализовать методы уведомления об обновлениях в конкретных классах
подписчиков. Большинству подписчиков потребуются некоторые
контекстные данные о событии. Его можно передать как аргумент метода
уведомления.
Получив уведомление, подписчик может также получить любые данные
прямо из уведомления. В этом случае издатель должен пройти через метод
обновления. Менее гибкий вариант - навсегда связать издателя с
подписчиком через конструктор.
Клиент должен создать всех необходимых подписчиков и зарегистрировать
их у соответствующих издателей.
• Применение
когда изменение состояния одного объекта может потребовать изменения
других объектов, а фактический набор объектов заранее неизвестен или
изменяется динамически

11.

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

12.

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

13.

14.

15.

16.

17.

18.

Double Buffer
• - это шаблон проектирования для того, чтобы сделать серию
последовательных операций мгновенной или одновременной.

19.

Game loop
• Game loop –
• отделить изменение игрового времени от пользовательского ввода и скорости
процессора.
• Update method
• cмоделировать набор независимых объектов, сообщая каждому обрабатывать по
одному кадру поведения за раз.

20.

Bytecode
• Придать поведению гибкость данных, закодировав их как инструкции для
виртуальной машины.

21.

• Subclass sandbox
• Определить поведение в подклассе, используя набор операций, предоставляемых его
базовым классом.
• Type object
• Разрешить гибкое создание новых «классов» путем создания одного класса, каждый
экземпляр которого представляет отдельный тип объекта.
• Component model
• Разрешить одному объекту охватывать несколько доменов, не связывая домены друг с
другом.
• Event queue
• Service locator
• Обеспечить глобальную точку доступа к службам, не привязываясь к конкретным
классам, которые их реализует.

22.

Шаблон создания: Singleton
• Проблема
• гарантировать, что у класса есть только
один экземпляр.
• обеспечить глобальную точку доступа к
этому экземпляру.
• Применение
• контроль доступа к какому-либо общему
ресурсу, например, базе данных или
файлу.
• Решение
• сделать конструктор по умолчанию
закрытым, чтобы другие объекты не могли
использовать оператор new
• создать статический метод создания,
который действует как конструктор. Под
капотом этот метод вызывает частный
конструктор для создания объекта и
сохраняет его в статическом поле. Все
последующие вызовы этого метода
возвращают кешированный объект.

23.

Шаблон создания: Factory method
• Проблема
• предоставить интерфейс для создания объектов в
суперклассе, позволяя подклассам изменять тип
создаваемых объектов.
• т.е. как создавать объекты таким образом, чтобы
фреймворк можно было повторно использовать в
разных контекстах использования приложениях /
платформах
• Решение
• Продукт объявляет интерфейс, общий для всех
объектов, которые могут быть созданы создателем
и его подклассами с конкретными реализациями.
• Класс Создатель объявляет метод, возвращающий
новые объекты продукта соответствием
интерфейсу Продукта.
• Конкретные Создатели переопределяют базовый
метод, с возвращением конкретного типа продукта.
• Применение
• Используется, если заранее не известны точные
типы и зависимости объектов, с которыми должен
работать код.
• Используется, если необходимо предоставить
пользователям системы способ расширения его
внутренних компонентов.
• Используется, если необходимо сэкономить
системные ресурсы, повторно используя
существующие объекты, вместо того, чтобы
каждый раз перестраивать их.

24.

Шаблон создания: Abstract Factory
• Проблема
• создать семейства связанных объектов без указания их
конкретных классов.
• Решение
• Составить матрицу различных типов продуктов в сравнении с
вариантами этих продуктов.
• Объявить абстрактные интерфейсы продуктов для всех типов
продуктов. Затем заставить все конкретные классы продуктов и
реализовать эти интерфейсы.
• Объявить абстрактный интерфейс фабрики с набором методов
создания для всех абстрактных продуктов.
• Реализовать набор конкретных фабричных классов, по одному
для каждого варианта продукта.
• Создать фабричный код инициализации где-нибудь в
приложении. Он должен создать экземпляр одного из
конкретных фабричных классов, в зависимости от
конфигурации приложения или текущей среды. Передать этот
объект фабрики всем классам, которые создают продукты.
• В коде найти все прямые вызовы конструкторов продуктов и
заменить их вызовами соответствующего метода создания
объекта фабрики.
• Применение
• Используется, когда код должен работать с различными
семействами связанных продуктов, но необходимо, чтобы он
зависел от конкретных классов этих продуктов - они могут быть
неизвестны заранее или требуется разрешить возможность
расширения в будущем.

25.

Структурный шаблон: Composite
• Проблема
• объединять объекты в древовидные структуры, а
затем работать с этими структурами, как если бы они
были отдельными объектами.
• Решение
• Убедится, что базовая модель приложения может быть
представлена ​в виде древовидной структуры.
Попробовать разбить его на простые элементы и
контейнеры.
• Объявить интерфейс компонента со списком методов,
которые имеют смысл как для простых, так и для
сложных компонентов.
• Создайте «лепестоквый» класс для представления
простых элементов. Программа может иметь
несколько разных конечных классов.
• Создать контейнерный класс для представления
сложных элементов, в котором создать массива для
хранения ссылок на подэлементы.
• Определить методы добавления и удаления дочерних
элементов в контейнере.
• Применение
• для реализации древовидной структуры объектов
• чтобы клиентский код одинаково обрабатывал как
простые, так и сложные элементы

26.

Структурный шаблон: Adapter
• Проблема
• позволить объектам с несовместимыми интерфейсами
взаимодействовать
• Решение
• Есть 2 класса с несовместимыми интерфейсами:
• Полезный класс обслуживания, который нельзя изменить (часто
сторонний, устаревший или с большим количеством существующих
зависимостей).
• Один или несколько классов клиентов, которым было бы полезно
использовать класс обслуживания.
• Объявить клиентский интерфейс и описать, как клиенты
взаимодействуют со службой.
• Создать класс адаптера и заставить его следовать клиентскому
интерфейсу с пока пустыми методами.
• Добавить поле в класс адаптера для хранения ссылки на объект
службы.
• Реализовать все методы клиентского интерфейса в классе
адаптера. Адаптер должен делегировать большую часть реальной
работы объекту службы, обрабатывая только интерфейс или
преобразование формата данных. Клиенты должны использовать
адаптер через клиентский интерфейс. Это позволит изменять или
расширять адаптеры, не затрагивая клиентский код.
• Применение
• если надо использовать существующий класс, но его интерфейс
несовместим с остальной частью кода
• если необходимо повторно использовать несколько
существующих подклассов, в которых отсутствуют некоторые
общие функции, которые нельзя добавить в суперкласс

27.

Структурный шаблон: Proxy
• Проблема
• предоставить замену или заполнитель для другого объекта. Проксисервер контролирует доступ к исходному объекту, позволяя выполнять
какие-либо действия до или после того, как запрос доходит до исходного
объекта.
• Решение
• Создать интерфейс службы, чтобы сделать объекты прокси и службы
взаимозаменяемыми. Можно также сделать прокси-сервер подклассом
класса службы, и таким образом он унаследует интерфейс службы.
• Создать прокси-класс с полем для хранения ссылки на службу для
создания и управления всем жизненным циклом своих служб.
• Реализовать методы прокси в соответствии с их целями.
• Ввести метода создания, который решает, получит ли клиент прокси или
реальную службу. Это может быть простой статический метод в проксиклассе или полноценный factory method.
• Применение
• при наличии службы, чрезмерно потребляющей системные ресурсы,
будучи постоянно включенной (не сразу инициализировать, а создавать
службу по мере необходимости)
• когда необходимо, чтобы только определенные клиенты могли
использовать объект службы; например, когда объекты являются
ключевыми частями операционной системы, а клиенты - различными
запущенными приложениями
• когда объект службы находится на удаленном сервере
• когда надо вести историю запросов к сервисному объекту
• когда нужно кэшировать результаты клиентских запросов и управлять
жизненным циклом этого кеша
• для возможности отклонения «тяжелого» объекта, когда нет клиентов,
которые его используют

28.

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

29.

Поведенческий шаблон: Iterator
• Проблема
• позволить перемещаться по элементам коллекции, не открывая
ее базовое представление (список, стек, дерево и т. д.)
• Решение
• Объявить интерфейс итератора, с методом выбора следующего
элемента из коллекции (+ методы выбора пред. элемента,
отслеживания текущей позиции, проверки конца итерации?)
• Объявить интерфейс коллекции и описать метод получения
итераторов с типом возвращаемого значения одинаковым с
интерфейсом итератора.
• Реализовать конкретные классы итераторов для коллекций,
которые должны быть доступны с помощью итераторов. Объект
итератора должен быть связан с одним экземпляром коллекции.
Обычно эта ссылка устанавливается через конструктор итератора.
• Реализовать интерфейс коллекции в классах коллекции для
предоставления клиенту ярлыка создания итераторов,
адаптированных для определенного класса коллекции. Объект
коллекции должен передать себя конструктору итератора, чтобы
установить связь между ними.
• Заменить весь код обхода коллекции с использованием
итераторов. Клиент выбирает новый объект-итератор каждый
раз, когда ему нужно перебрать элементы коллекции.
• Применение
• когда коллекция имеет сложную структуру данных, но надо
скрыть ее сложность от клиентов (для удобства или
безопасности)
• чтобы уменьшить дублирование кода обхода приложении
• когда необходимо, чтобы код мог перемещаться по различным
структурам данных или когда типы этих структур заранее
неизвестны
English     Русский Правила