Паттерны проектирования в метафорах

 

На данный момент статей о паттернах проектирования в сети предостаточно. Эта статья — попытка расставить все по местам (велком метод Фейнмана). Я постарался просто описать паттерны в метафорах, не погружаясь в реализацию. Да, я не заявляю, что все метафоры придумал самостоятельно. В большинстве случаев это примеры куда более компетентных авторов. Так же, привожу для удобства ссылки на википедию — основной источник информации. 

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

Паттерны по принципу применения делятся на три вида. 

Порождающие паттерны 

Фабрика 

Предположим, вам нужно упаковать какую-то продукцию. Вы заказываете на фабрике коробки определенных размеров. Вас совершенно не интересует каким образом устроено производство. Вы просто получаете коробки по заданным параметрам. 

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

Абстрактная фабрика 

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

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

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

Строитель 

В отличии от фабрик паттерн «строитель» позволяет создавать объекты с большим количеством параметров при этом, не внося эти параметры в конструктор объекта. Хороший пример — это многокомпонентный сэндвич. Вы можете сделать сэндвич просто с ветчиной, а можете добавить соус, сыр или салат. Паттерн призван решить проблему telescoping constructor, когда конструктор разрастается от передаваемых ему параметров. 

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

Фабричный метод 

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

Прототип 

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

Синглтон 

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

Структурные паттерны 

Адаптер 

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

Мост 

Паттерн призывающий использовать абстракции, а не реализации классов. Абстрактный класс «фигура» определяет методы и основные свойства геометрических фигур, квадрат, круг и треугольник унаследованы от него. В процессе реализации необходимо добавить фигурам свойство цвета. Лучшим решением будет добавить свойство типа «цвет» в класс «фигура» и в дальнейшем подставлять в него наследников класса «цвет». Так вот, мостом, в данном случае, и будет внедрение связи между цветом и фигурой. То есть мост объединяет две сущности. 

Компоновщик 

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

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

Декоратор 

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

Фасад 

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

Приспособленец 

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

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

Заместитель 

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

Поведенческие паттерны 

Цепочка обязанностей 

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

Команда 

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

Интерпретатор 

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

Итератор 

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

Посредник 

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

Хранитель 

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

Наблюдатель 

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

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

Состояние 

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

Стратегия 

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

Шаблонный метод 

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

Посетитель 

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