Краткое изложение шаблонов проектирования GoF

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

Если вы не знали, кто такой Морис Корнелис Эшер, то он был одним из величайших художников двадцатого века, создававшим математические ксилографии, литографии и меццо-тинто.

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

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

Содержание

  • Что такое шаблон проектирования?
  • Описание шаблонов проектирования
  • Каталог шаблонов проектирования
  • Организация каталога
  • Как шаблоны проектирования решают проблемы проектирования

Что такое шаблон проектирования?

Кристофер Александер говорит:

«Каждый шаблон описывает проблему, которая снова и снова возникает в нашей среде, а затем описывает суть решения этой проблемы таким образом, что вы можете использовать это решение миллион раз, никогда не повторяя его дважды. ” [AIS+77, страница х]

Вероятно, лучший способ классифицировать шаблоны проектирования — это рассказать об их четырех основных элементах:

  1. Имя шаблона — это просто имя, описывающее шаблон. Обычно это одно или два слова в связи с их Решением и Последствиями. Присвоив имя шаблона, мы можем использовать его, чтобы общаться с нашими коллегами, писать документацию и многое другое без двусмысленности.
  2. Проблема описывает, когда мы применяем шаблон. Это может быть представление, например, как реализовать алгоритм или структуру данных в виде объекта.
  3. Решение описывает элементы, которые мы должны использовать для реализации шаблона проектирования. Эти элементы могут быть связаны сами с собой, а сотрудничество и ответственность более важны.
  4. Последствия – это результат и компромисс, который должен иметь шаблон дизайна.

Описание шаблона проектирования

В книге Шаблон проектирования — элементы многоразового объектно-ориентированного программного обеспечения шаблоны проектирования описываются в едином формате. Каждый шаблон разделен на разделы в соответствии со следующим шаблоном.

Название шаблона и классификация

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

Намерение

Следующий вопрос: что делает шаблон проектирования? Каково его обоснование и цель? Какая конкретная проблема или проблема дизайна решается?

Также известен как

Другие известные названия шаблона, если таковые имеются.

Мотивация

Сценарий, который иллюстрирует проблему проектирования и то, как структура классов и объектов в шаблоне решает проблему.

Применимость

В каких ситуациях можно применить шаблон проектирования? Какие примеры плохого дизайна можно устранить с помощью шаблона? Как распознать эти ситуации?

Состав

Графическое представление классов в шаблоне с использованием нотации, основанной на методе объектного моделирования (OMT) [RBP+91]. Я также использую диаграммы взаимодействия [JCJO92, Boo94], чтобы проиллюстрировать последовательность запросов и взаимодействия между объектами.

Участники

Классы и/или объекты, участвующие в шаблоне проектирования, и их обязанности.

Сотрудничество

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

Последствия

Как шаблон поддерживает свои цели? Каковы компромиссы и результаты использования шаблона?

Выполнение

О каких подводных камнях, подсказках или приемах следует помнить при реализации шаблона?

Образец кода

Фрагменты кода, которые иллюстрируют, как можно реализовать шаблон на C++ или Smalltalk.

Известные виды использования

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

Связанные шаблоны

Когда шаблон проектирования тесно связан с этим?

Каталог шаблонов проектирования

  • Абстрактная фабрика Предоставляет интерфейс для создания семейств связанных или зависимых объектов без указания их конкретных классов.
  • Адаптер — преобразует интерфейс класса в другой интерфейс, ожидаемый клиентами. Адаптер позволяет классам работать вместе, что иначе было бы невозможно из-за несовместимых интерфейсов.
  • Мост — отделите абстракцию от ее реализации, чтобы они могли работать совершенно независимо друг от друга.
  • Builder — отделите построение сложного объекта от его представления, чтобы один и тот же процесс построения мог создавать разные представления.
  • Цепочка ответственности. Избегайте привязки отправителя запроса к его получателю, предоставляя более чем одному объекту возможность обработать запрос. Цепляйте принимающий объект и передайте запрос по цепочке, пока объект не обработает его.
  • Команда – инкапсулируйте запрос в виде объекта, что позволит вам параметризовать клиентов с различными запросами, ставить в очередь или регистрировать запросы, а также поддерживать операции отмены.
  • Composite — объединение объектов в древовидную структуру для представления иерархии часть-целое. Composite позволяет клиентам единообразно обрабатывать отдельные объекты и композиции объектов.
  • Декоратор — динамически присваивайте объекту дополнительные обязанности. Декораторы предоставляют гибкую альтернативу подклассам для расширения функциональности.
  • Фасад. Предоставляет унифицированный интерфейс для набора интерфейсов в подсистеме. Фасад определяет высокоуровневый интерфейс, упрощающий использование подсистемы.
  • Фабричный метод. Определите интерфейс для создания объекта, но пусть подклассы решают, какой класс создавать. Фабричный метод позволяет классу откладывать создание экземпляров до подклассов.
  • Легкий вес — используйте совместное использование для эффективной поддержки большого количества мелких объектов.
  • Интерпретатор. Для данного языка определите представление для его грамматики вместе с интерпретатором, который использует представление для интерпретации предложений на языке.
  • Итератор — предоставляет способ последовательного доступа к элементам агрегатного объекта без раскрытия его базового представления.
  • Посредник – определите объект, который инкапсулирует способ взаимодействия набора объектов. Посредник способствует слабой связи, не позволяя объектам явно ссылаться друг на друга, и позволяет независимо изменять их взаимодействие.
  • Memento — не нарушая инкапсуляции, фиксируйте и преобразуйте внутреннее состояние объекта, чтобы объект можно было вернуть в это состояние позже.
  • Наблюдатель. Определите зависимость между объектами по принципу «один ко многим», чтобы при изменении состояния одного объекта все его зависимые объекты уведомлялись и обновлялись автоматически.
  • Прототип. Укажите типы объектов, которые необходимо создать, используя прототип, и создайте новые объекты, скопировав этот прототип.
  • Прокси-сервер — предоставление суррогата или заполнителя для другого объекта, чтобы управлять доступом к нему.
  • Singleton. Убедитесь, что класс имеет только один экземпляр, и предоставьте к нему глобальную точку доступа.
  • Состояние — позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Объект изменит свой класс.
  • Стратегия. Определите семейство алгоритмов, инкапсулируйте каждый из них и сделайте их взаимозаменяемыми. Стратегия позволяет алгоритму изменяться независимо от клиентов, которые его используют.
  • Шаблонный метод — определение скелета алгоритма в операции, перенос некоторых шагов на подклассы. Шаблонный метод позволяет подклассам переопределять определенные шаги алгоритма без изменения структуры алгоритма.
  • Посетитель — представляет операцию, которая должна выполняться над элементами структуры объекта. Посетитель позволяет определить новую операцию без изменения классов элементов, над которыми она работает.

Организация каталога

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

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

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

Как шаблоны проектирования решают проблемы проектирования

Поиск подходящих объектов

Объектно-ориентированное программирование состоит из объектов. Объект упаковывает как данные, так и процедуры, которые работают с этими данными. Процедуры обычно называются методами или операциями. Объект выполняет операцию, когда получает запрос от клиента.

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

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

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

Определение степени детализации объекта

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

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

Указание интерфейсов объектов

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

Тип – это имя, используемое для обозначения определенного интерфейса. Мы говорим об объекте, имеющем тип «Окно», если он принимает все запросы на операции, определенные в интерфейсе с именем «Окно». Два объекта одного и того же типа должны иметь только общие части своих интерфейсов. Интерфейсы могут содержать другие интерфейсы в качестве подмножеств. Мы говорим, что тип является подтипом другого, если его интерфейс содержит интерфейс его супертипа.

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

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

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

Например, шаблон Memento описывает, как инкапсулировать и сохранить внутреннее состояние объекта, чтобы объект можно было восстановить в это состояние позже.

Указание реализации объекта

Реализация объекта определяется его классом.

Объекты создаются путем создания экземпляра класса. Говорят, что объект является экземпляром класса. В процессе создания экземпляра класса выделяется хранилище для внутренних данных объекта (состоящих из переменных экземпляра) и связываются операции с этими данными.

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

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

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

Класс против наследования интерфейса

Важно понимать разницу между классом объекта и его типом.

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

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

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

Внедрение механизмов повторного использования в работу

Наследование и композиция

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

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

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

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

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

Делегирование

Делегирование — это способ сделать композицию столь же мощной для повторного использования, как и наследование [Lie86, JZ91]. При делегировании дваобъекта участвуют в обработке запроса: принимающий объект делегирует операции своему делегату.

Отличается от наследования тем, что доступ к нему больше не осуществляется через this или self. Например, вместо того, чтобы делать класс Window подклассом Rectangle, класс Window может повторно использовать поведение Rectangle, сохраняя переменную экземпляра Rectangle и делегируяспецифичное для Rectangle поведение.

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

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

Несколько шаблонов проектирования используют делегирование. От него зависят шаблоны State, Strategy и Visitor.

Наследование и параметризованные типы

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

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

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

Связь структур времени выполнения и времени компиляции

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

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

На этих диаграммах простая линия со стрелкой обозначает знакомство. Линия стрелки с ромбом в основании обозначает агрегацию:

Проектирование для изменений

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

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

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

  • Создание объекта путем явного указания класса. Указание имени класса при создании объекта фиксирует конкретную реализацию, а не конкретный интерфейс. Шаблоны проектирования: Абстрактная фабрика, Фабричный метод, Прототип.
  • Зависимость от конкретных операций. Когда вы указываете конкретную операцию, вы соглашаетесь с одним способом удовлетворения запроса. Избегая жестко закодированного запроса, вы упрощаете изменение способа удовлетворения запроса как во время компиляции, так и во время выполнения. Шаблоны проектирования: цепочка ответственности, команда.
  • Алгоритмические зависимости. Алгоритмы часто расширяются, оптимизируются и заменяются во время разработки и повторного использования. Объект, зависящий от алгоритма, должен будет измениться при изменении алгоритма. Поэтому алгоритмы, которые могут измениться, должны быть изолированы. Шаблоны проектирования: Builder, Iterator, Strategy, Template Method, Visitor.
  • Надежная связь. Сильная связь приводит к монолитным системам, в которых нельзя изменить или удалить класс, не разобравшись и не изменив многие другие классы. Система становится плотной массой, которую трудно изучать, портировать и поддерживать. В шаблонах проектирования используются такие методы, как абстрактное связывание и наслоение, для продвижения слабосвязанных систем. Паттерны проектирования: Абстрактная фабрика, Мост, Цепочка ответственности, Команда, Фасад, Посредник, Наблюдатель.
  • Расширение функциональности за счет создания подклассов. Настройка объекта путем создания подклассов часто бывает непростой задачей. Каждый новый класс имеет фиксированные накладные расходы на реализацию. Определение подкласса также требует глубокого понимания родительского класса. Композиция объектов в целом и делегирование в частности предоставляют гибкие альтернативы наследованию для комбинирования поведения. Новые функциональные возможности могут быть добавлены в приложение путем компоновки существующих объектов новыми способами, а не путем определения новых подклассов существующих классов. Шаблоны проектирования: Мост, Цепочка ответственности, Композит, Декоратор, Наблюдатель, Стратегия.