Как учить паттерны проектирования
Способ качественно изучить паттерны проектирования
Привет, Хабр! Когда я изучал паттерны проектирования, я делал это с помощью прочтения двух книг: простую и понятную книгу от Head First одновременно со сложной и менее понятной книгой от Банды Четырех. Ниже описан мой опыт того, как именно я это делал, плюс выводы, впечатления и советы
Дисклеймер: я не утверждаю, что паттерны проектирования безусловно необходимы, и что если человек не прочитал паттерны Банды Четырех, то он не может называться разработчиком. Я лишь делюсь своим опытом, как изучал паттерны именно я. И надеюсь, что кому-то этот опыт окажется полезным
Итак, для изучения паттернов читал я одновременно следующие книги:
Первая книга читается легко и просто, там всё понятно; вторая книга читается сложнее, она более техническая, но там и больше полезной информации.
В чем смысл чтения двух книг одновременно: можно идти последовательно по темам (≈ паттернам), сначала читать главу из простой книги, а потом главу на ту же тему из сложной. У такого подхода есть следующие плюсы:
Более сложная информация из сложной книги усваивается качественнее за счет того, что ты уже подготовлен к потреблению этой информации
Информация из обеих книг в целом усваивается лучше за счет повторения материала и взгляда на одну и ту же проблему с разных сторон
Соответствие глав/тем
Ниже я привел некоторый план прочтения обеих книг (объяснение, как читать этот план, находится под самим планом):
Все темы можно поделить на следующие группы:
Введения и предисловия
Паттерны, хорошо описанные в обеих книгах. Это те паттерны, которые прошли проверку временем и были доступно описаны Head First
Паттерны, хорошо описанные только Бандой Четырех. Это менее популярные паттерны, но тоже имеющие право быть изученными. Head First уделяет этим паттернам буквально по несколько страниц, в то время как Банда Четырех описывает эти паттерны так же, как и все остальные
Заключения от Банды Четырех. Здесь, помимо самого заключения, содержится пример использования паттернов на практике
Впечатления от книг
Head First
Специфическая обложка и кол-во страниц (600+) для такой, казалось бы, не очень серьезной книги могут оттолкнуть от прочтения, но на самом книга читается очень просто, интересно, местами даже забавно, но при этом рассказываются там вполне полезные вещи. На фоне Банды Четырех чтение этой книги воспринимается чуть ли ни как отдых.
Читать эту книгу имеет смысл последовательно, а не в случайном порядке. Именно поэтому порядок глав/тем при одновременном чтении обеих книг соответствует Head First.
Примеры кода написаны на Java.
Банда Четырех
В отличие от Head First, здесь нет определенного порядка. Книга является скорее справочником паттернов, и читать ее от корки до корки необходимости нет. Информация здесь более сухая и техническая, но рассмотрено значительно больше деталей и нюансов, связанных с реализацией и применением как конкретных паттернов, так и их комбинаций.
Примеры кода написаны на C++ и SmallTalk. Последний язык, как мне показалось, имеет довольно специфический синтаксис, но в целом можно понять, что хотят показать авторы.
Итого
Могу предложить 4 пути:
Повторить мой путь и прочитать обе книги одновременно. Такой вариант подойдет для тех, кто хочет начать изучать паттерны, но при этом хочет изучить их сразу на достаточно хорошем уровне
Прочитать только книгу от Банды Четырех. Подходит для тех, кто уже неплохо знает паттерны и хочет изучить их глубже
Прочитать только книгу от Head First. Подходит для тех, кто хочет начать изучать паттерны, но не хочет углубляться слишком сильно
Не читать ничего. Ибо кому вообще нужны эти паттерны в реальном мире
На этом всё. Надеюсь, кому-нибудь эта статья будет полезна, и спасибо за внимание!
Основы паттернов проектирования
Введение в паттерны проектирования
Что представляют собой паттерны проектирования? Паттерн представляет определенный способ построения программного кода для решения часто встречающихся проблем проектирования. В данном случае предполагается, что есть некоторый набор общих формализованных проблем, которые довольно часто встречаются, и паттерны предоставляют ряд принципов для решения этих проблем.
Хотя идея паттернов как способ описания решения распространенных проблем в области проектирования появилась довольно давно, но их популярность стала расти во многом благодаря известной работе четырех авторов Эриха Гаммы, Ричарда Хелма, Ральфа Джонсона, Джона Влиссидеса, которая называлась «Design Patterns: Elements of Reusable Object-Oriented Software» (на русском языке известна как «Приемы объектно-ориентированного проектирования. Паттерны проектирования») и которая вышла в свет в 1994 году. А сам коллектив авторов нередко называют «Банда четырёх» или Gang of Four или сокращенно GoF. Данная книга по сути являлась первой масштабной попыткой описать распространенные способы проектирования программ. И со временем применение паттернов стало считаться хорошей практикой программирования.
Что же дает нам применение паттернов? При написании программ мы можем формализовать проблему в виде классов и объектов и связей между ними. И применить один из существующих паттернов для ее решения. В итоге нам не надо ничего придумывать. У нас уже есть готовый шаблон, и нам только надо его применить в конкретной программе.
Причем паттерны, как правило, не зависят от языка программирования. Их принципы применения будут аналогичны и в C#, и в Jave, и в других языках. Хотя в рамках данного руководства мы будем говорить о паттернах в контексте языка C#.
Также мышление паттернами упрощает групповую разработку программ. Зная применяемый паттерн проектирования и его основные принципы другому программисту будет проще понять его реализацию и использовать ее.
В то же время не стоит применять паттерны ради самих паттернов. Хорошая программа предполагает использование паттернов. Однако не всегда паттерны упрощают и улучшают программу. Неоправданное их использование может привести к усложнению программного кода, уменьшению его качества. Паттерн должен быть оправданным и эффективным способом решения проблемы.
Существует множество различных паттернов, которые решают разные проблемы и выполняют различные задачи. Но по своему действию их можно объединить в ряд групп. Рассмотрим некоторые группы паттернов. В основу классификации основных паттернов положена цель или задачи, которые определенный паттерн выполняет.
Порождающие паттерны
Порождающие паттерны — это паттерны, которые абстрагируют процесс инстанцирования или, иными словами, процесс порождения классов и объектов. Среди них выделяются следующие:
Абстрактная фабрика (Abstract Factory)
Фабричный метод (Factory Method)
Цепочка обязанностей (Chain of responsibility)
Шаблонный метод (Template method)
Существуют и другие классификации паттернов в зависимости от того, относится паттерн к классам или объектам.
Паттерны классов описывают отношения между классами посредством наследования. Отношения между классами определяются на стадии компиляции. К таким паттернам относятся:
Фабричный метод (Factory Method)
Шаблонный метод (Template Method)
Как вы освоили шаблоны проектирования?
1. Открываете папку с паттерном.
2. Читаете README.md с описание задачи.
3. Открываете exercise.php и пытаетесь решить задачу, применив соответствующий паттерн.
4. При необходимости вспоминаете теорию: github.com/domnikl/DesignPatternsPHP
5. Сверяетесь с решением в solution.php.
Когда начался бум и восторг вокруг концепции паттернов проектирования, выкрики «GoF рулит!» и так далее, я озадачился тем, чтобы понять, что за шум?
По аналогии в проектировании софта имееются свои архитектурные вопросы вроде разбиения приложения на компоненты/модули, организации зависимостей между ними, распределение функциональных обязанностей и т.п. Как ловко подметили авторы книжки из этой банды четырех (The «Gang of Four») в нашей индустрии можно также выделить некоторе количество типовых шаблонов, проверенных на практике, чтобы тем самым не наступать на уже обойденные другими грабли.
К изучению паттернов я дам такие советы:
2) Постарайтесь осознать, доводилось ли вам сталкиваться в работе раньше с чем-то, что является или могло бы легко стать одним из шаблонов. Где получалось применить концепт верно, а где из-за этого только проблемы были.
Шаблоны проектирования простым языком. Часть первая. Порождающие шаблоны
Шаблоны проектирования — это руководства по решению повторяющихся проблем. Это не классы, пакеты или библиотеки, которые можно было бы подключить к вашему приложению и сидеть в ожидании чуда. Они скорее являются методиками, как решать определенные проблемы в определенных ситуациях.
Википедия описывает их следующим образом:
Шаблон проектирования, или паттерн, в разработке программного обеспечения — повторяемая архитектурная конструкция, представляющая собой решение проблемы проектирования, в рамках некоторого часто возникающего контекста.
Будьте осторожны
Также заметьте, что примеры ниже написаны на PHP 7. Но это не должно вас останавливать, ведь принципы остаются такими же.
Типы шаблонов
Шаблоны бывают следующих трех видов:
Если говорить простыми словами, то это шаблоны, которые предназначены для создания экземпляра объекта или группы связанных объектов.
Порождающие шаблоны — шаблоны проектирования, которые абстрагируют процесс инстанцирования. Они позволяют сделать систему независимой от способа создания, композиции и представления объектов. Шаблон, порождающий классы, использует наследование, чтобы изменять наследуемый класс, а шаблон, порождающий объекты, делегирует инстанцирование другому объекту.
Существуют следующие порождающие шаблоны:
Простая фабрика (Simple Factory)
В объектно-ориентированном программировании (ООП), фабрика — это объект для создания других объектов. Формально фабрика — это функция или метод, который возвращает объекты изменяющегося прототипа или класса из некоторого вызова метода, который считается «новым».
Пример из жизни: Представьте, что вам надо построить дом, и вам нужны двери. Было бы глупо каждый раз, когда вам нужны двери, надевать вашу столярную форму и начинать делать дверь. Вместо этого вы делаете её на фабрике.
Простыми словами: Простая фабрика генерирует экземпляр для клиента, не раскрывая никакой логики.
Перейдем к коду. У нас есть интерфейс Door и его реализация:
И затем мы можем использовать всё это:
Когда использовать: Когда создание объекта — это не просто несколько присвоений, а какая-то логика, тогда имеет смысл создать отдельную фабрику вместо повторения одного и того же кода повсюду.
Фабричный метод (Fabric Method)
Фабричный метод — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, данный шаблон делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.
Пример из жизни: Рассмотрим пример с менеджером по найму. Невозможно одному человеку провести собеседования со всеми кандидатами на все вакансии. В зависимости от вакансии он должен распределить этапы собеседования между разными людьми.
Простыми словами: Менеджер предоставляет способ делегирования логики создания экземпляра дочерним классам.
Перейдём к коду. Рассмотрим приведенный выше пример про HR-менеджера. Изначально у нас есть интерфейс Interviewer и несколько реализаций для него:
Теперь создадим нашего HiringManager :
И теперь любой дочерний класс может расширять его и предоставлять необходимого интервьюера:
Когда использовать: Полезен, когда есть некоторая общая обработка в классе, но необходимый подкласс динамически определяется во время выполнения. Иными словами, когда клиент не знает, какой именно подкласс ему может понадобиться.
Абстрактная фабрика (Abstract Factory)
Абстрактная фабрика — порождающий шаблон проектирования, предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов. Шаблон реализуется созданием абстрактного класса Factory, который представляет собой интерфейс для создания компонентов системы (например, для оконного интерфейса он может создавать окна и кнопки). Затем пишутся классы, реализующие этот интерфейс.
Пример из жизни: Расширим наш пример про двери из простой фабрики. В зависимости от ваших нужд вам понадобится деревянная дверь из одного магазина, железная дверь — из другого или пластиковая — из третьего. Кроме того, вам понадобится соответствующий специалист: столяр для деревянной двери, сварщик для железной двери и так далее. Как вы можете заметить, тут есть зависимость между дверьми.
Простыми словами: Фабрика фабрик. Фабрика, которая группирует индивидуальные, но связанные/зависимые фабрики без указания их конкретных классов.
Обратимся к коду. Используем пример про двери. Сначала у нас есть интерфейс Door и несколько его реализаций:
Затем у нас есть несколько DoorFittingExpert для каждого типа дверей:
Как вы можете заметить, фабрика деревянных дверей инкапсулирует столяра и деревянную дверь, а фабрика железных дверей инкапсулирует железную дверь и сварщика. Это позволило нам убедиться, что для каждой двери мы получим нужного нам эксперта.
Когда использовать: Когда есть взаимосвязанные зависимости с не очень простой логикой создания.
Строитель (Builder)
Строитель — порождающий шаблон проектирования, который предоставляет способ создания составного объекта. Предназначен для решения проблемы антипаттерна «Телескопический конструктор».
Пример из жизни: Представьте, что вы пришли в McDonalds и заказали конкретный продукт, например, БигМак, и вам готовят его без лишних вопросов. Это пример простой фабрики. Но есть случаи, когда логика создания может включать в себя больше шагов. Например, вы хотите индивидуальный сэндвич в Subway: у вас есть несколько вариантов того, как он будет сделан. Какой хлеб вы хотите? Какие соусы использовать? Какой сыр? В таких случаях на помощь приходит шаблон «Строитель».
Простыми словами: Шаблон позволяет вам создавать различные виды объекта, избегая засорения конструктора. Он полезен, когда может быть несколько видов объекта или когда необходимо множество шагов, связанных с его созданием.
Давайте я покажу на примере, что такое «Телескопический конструктор». Когда-то мы все видели конструктор вроде такого:
Как вы можете заметить, количество параметров конструктора может резко увеличиться, и станет сложно понимать расположение параметров. Кроме того, этот список параметров будет продолжать расти, если вы захотите добавить новые варианты. Это и есть «Телескопический конструктор».
Затем мы берём «Строителя»:
Когда использовать: Когда может быть несколько видов объекта и надо избежать «телескопического конструктора». Главное отличие от «фабрики» — это то, что она используется, когда создание занимает один шаг, а «строитель» применяется при множестве шагов.
Прототип (Prototype)
Задаёт виды создаваемых объектов с помощью экземпляра-прототипа и создаёт новые объекты путём копирования этого прототипа. Он позволяет уйти от реализации и позволяет следовать принципу «программирование через интерфейсы». В качестве возвращающего типа указывается интерфейс / абстрактный класс на вершине иерархии, а классы-наследники могут подставить туда наследника, реализующего этот тип.
Пример из жизни: Помните Долли? Овечка, которая была клонирована. Не будем углубляться, главное — это то, что здесь все вращается вокруг клонирования.
Простыми словами: Прототип создает объект, основанный на существующем объекте при помощи клонирования.
То есть он позволяет вам создавать копию существующего объекта и модернизировать его согласно вашим нуждам, вместо того, чтобы создавать объект заново.
Обратимся к коду. В PHP это может быть легко реализовано с использованием clone :
Затем он может быть клонирован следующим образом:
Также вы можете использовать волшебный метод __clone для изменения клонирующего поведения.
Когда использовать: Когда необходим объект, похожий на существующий объект, либо когда создание будет дороже клонирования.
Одиночка (Singleton)
Одиночка — порождающий шаблон проектирования, гарантирующий, что в однопроцессном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Пример из жизни: В стране одновременно может быть только один президент. Один и тот же президент должен действовать, когда того требуют обстоятельства. Президент здесь является одиночкой.
Простыми словами: Обеспечивает тот факт, что создаваемый объект является единственным объектом своего класса.
Вообще шаблон одиночка признан антипаттерном, необходимо избегать его чрезмерного использования. Он необязательно плох и может иметь полезные применения, но использовать его надо с осторожностью, потому что он вводит глобальное состояние в ваше приложение и его изменение в одном месте может повлиять на другие части приложения, что вызовет трудности при отладке. Другой минус — это то, что он делает ваш код связанным.
Прим. перев. Подробнее о подводных камнях шаблона одиночка читайте в нашей статье.
Перейдем к коду. Чтобы создать одиночку, сделайте конструктор приватным, отключите клонирование и расширение и создайте статическую переменную для хранения экземпляра:
Паттерны проектирования для новичков
Внимание! Материал был обновлён и разделён на три части. Предлагаем вам ознакомиться с ним по следующим ссылкам:
Если вы когда-либо интересовались, что представляют собой шаблоны проектирования, то добро пожаловать. В этой статье я расскажу, что это такое, зачем они нужны, как их использовать, и приведу примеры наиболее распространенных шаблонов на PHP.
Что такое шаблоны проектирования
Шаблоны проектирования — это проверенные и готовые к использованию решения часто возникающих в повседневном программировании задач. Это не класс и не библиотека, которую можно подключить к проекту, это нечто большее. Шаблон проектирования, подходящий под задачу, реализуется в каждом конкретном случае. Кроме того, он не зависит от языка программирования. Хороший шаблон легко реализуется в большинстве, если не во всех языках, в зависимости от выразительных средств языка. Следует, однако, помнить, что такой шаблон, будучи примененным неправильно или к неподходящей задаче, может принести немало проблем. Тем не менее, правильно примененный шаблон поможет решить задачу легко и просто.
Существует три типа шаблонов:
Структурные шаблоны определяют отношения между классами и объектами, позволяя им работать совместно.
Порождающие шаблоны предоставляют механизмы инициализации, позволяя создавать объекты удобным способом.
Поведенческие шаблоны используются для того, чтобы упростить взаимодействие между сущностями.
Зачем нужны шаблоны проектирования
Шаблон проектирования, по своей сути, это продуманное решение той или иной задачи. Если вы столкнулись с известной задачей, почему бы не использовать готовое решение, проверенное опытом?
Пример
Давайте представим, что вам необходимо объединить два класса, которые выполняют различные операции в зависимости от ситуации. Эти классы интенсивно используются существующей системой, что не позволяет удалить один из них и добавить его функциональность во второй. Кроме того, изменение кода потребует его тщательного тестирования, поскольку такой рефакторинг ведет к неизбежным ошибкам. Вместо этого вы можете реализовать шаблоны «Стратегия» и «Адаптер» и с их помощью решить задачу.
Просто, не правда ли? Давайте посмотрим поближе на шаблон «Стратегия».
Паттерн проектирования «Стратегия»
Стратегия — поведенческий шаблон, который позволяет выбрать поведение программы в процессе выполнения в зависимости от контекста путем инкапсуляции нескольких алгоритмов в разных классах.
Где это можно использовать
Представьте, что вы разрабатываете класс, который может создать или обновить запись в базе данных. В обоих случаях входные параметры будут одни и те же (имя, адрес, номер телефона и т. п.), но, в зависимости от ситуации, он будет должен использовать различные функции для обновления и создания записи. Можно каждый раз переписывать условие if/else, а можно создать один метод, который будет принимать контекст:
Обычно шаблон «Стратегия» подразумевает инкапсуляцию алгоритмов в классы, но в данном случае это излишне. Помните, что вы не обязаны следовать шаблону слово в слово. Любые варианты допустимы, если они решают задачу и соответствуют концепции.
Шаблон «Адаптер»
Адаптер — структурный шаблон, который позволяет использовать класс, реализующий нужные функции, но имеющий неподходящий интерфейс.
Также он позволяет изменить некоторые входные данные для совместимости с интерфейсом внутреннего класса.
Как его использовать?
Другое название адаптера — «Обертка». Он «оборачивает» новый интерфейс вокруг класса для его использования. Классический пример: вам надо создать класс предметной модели, имея классы объектов в базе данных. Вместо того, чтобы обращаться к табличным классам напрямую и вызывать их методы по одному, вы можете инкапсулировать вызовы этих методов в одном методе в адаптере. Это не только позволит повторно использовать набор операций, но и избавит вас от постоянного переписывания большого количества кода, если вам потребуется выполнить тот же набор действий в другом месте.
Сравните два примера.
Без адаптера:
Если нам придется использовать такой код повторно, мы будем вынуждены переписывать все это заново.
С использованием адаптера:
Мы можем создать класс-обертку Account :
Теперь мы можем использовать класс Account каждый раз и, кроме того, мы можем добавить в него дополнительные функции.
Шаблон «Метод-фабрика»
Фабрика — порождающий шаблон, который представляет собой класс с методом для создания различных объектов.
Основная цель этого шаблона — инкапсулировать процедуру создания различных классов в одной функции, которая в зависимости от переданного ей контекста возвращает необходимый объект.
Как его использовать?
Сначала создадим три класса:
Теперь мы можем написать нашу фабрику:
На выходе должен получиться HTML со всеми типами кнопок. Таким образом мы получили возможность указать, кнопку какого типа мы хотим получить, и использовать код повторно.
Шаблон «Декоратор»
Декоратор — это структурный шаблон, который позволяет добавить новое поведение объекту в процессе выполнения программы в зависимости от ситуации.
Цель — в расширении поведения конкретного объекта без необходимости изменять поведение базового класса. Это позволит использовать несколько декораторов одновременно. Этот шаблон — альтернатива наследованию. В отличие от наследования, декоратор добавляет поведение в процессе выполнения программы.
Для реализации декоратора нам понадобится:
Как его использовать?
Предположим, что у нас есть объект, который должен иметь определенное поведение в определенной ситуации. Например, у нас есть HTML-ссылка для выхода из аккаунта, которая должна по-разному показываться в зависимости от того, на какой странице мы находимся. Это тот самый случай, когда нам помогут декораторы.
Сначала определимся, какие «декорации» нам нужны:
Теперь мы можем написать сами декораторы:
Теперь мы можем использовать их так:
Обратите внимание, как можно использовать несколько декораторов на одном объекте. Все они используют функцию __call для вызова оригинального метода. Если мы войдем в аккаунт и перейдем на заглавную страницу, результат будет такой:
Шаблон «Одиночка»
Одиночка — порождающий шаблон, который позволяет убедиться, что в процессе выполнения программы создается только один экземпляр класса с глобальным доступом.
Его можно использовать как точку «координации» для других объектов, поскольку поля «Одиночки» будут одинаковы для всех, кто его вызывает.
Как его использовать?
Теперь мы можем получить доступ к сессии из различных участков кода, даже из других классов. Метод getInstance всегда будет возвращать одну и ту же сессию.
Заключение
В этой статье мы рассмотрели только наиболее часто встречающиеся шаблоны из множества. Если вы хотите узнать больше о шаблонах проектирования, вы найдете достаточно информации на Википедии. Для более полной информации обратите внимание на знаменитую книгу «Приемы объектно-ориентированного проектирования» «Банды четырех».
И последнее: при использовании того или иного шаблона убедитесь, что вы решаете задачу правильным способом. Как уже упоминалось, при неправильном использовании шаблоны проектирования могут доставить больше проблем, чем решить. Но при правильном — их пользу нельзя переоценить.