Для чего нужны паттерны проектирования
Для чего нужны шаблоны проектирования
Все чаще и чаще я слышу от разработчиков и читаю в статьях, что шаблоны проектирования (они же дизайн-паттерны) никому не нужны. Мол, они появились во времена «цветения» UML, RUP, CASE систем и прочих чересчур «сложных» инструментов, подходов и практик. А сейчас самое важное — это код рабочий написать, да побыстрее. На умные толстые книжки ни у кого нет времени, разве что для прохождения собеседования. Тех, кто хочет обсудить данную тему, прошу под кат.
Немного воспоминаний из молодости
Когда я учился в университете, нам преподавали в рамках одного из курсов шаблоны проектирования. На тот момент они казались мне чем-то наподобие сферического коня в вакууме, потому что практического опыта их применения я не имел (это был третий или начало четвертого курса много лет назад). Запомнить кто из них кто тоже было достаточно сложно, не говоря уже о тонкостях и деталях. Тем не менее, вопросы по шаблонам проектирования задавали в обязательном порядке на каждом собеседовании на работу. Кандидатам приходилось раздувать щеки и доказывать как круты разные шаблоны (особенно Singleton), видя их в жизни максимум раз-другой на страницах книжек.
Но ведь совсем не глупые люди придумали шаблоны проектирования:
Дальше продолжать исторические хроники смысла нет. Это была первая книга, из которой наше поколение черпало свои знания по шаблонам проектирования и пыталось применять их в своей работе. Она считается классикой в этой тематике и обязательна к прочтению.
Через некоторое время работы я начал замечать, что даже теоретические знания шаблонов проектирования помогают мне понять чужой код гораздо быстрее. А это особенно важно на старте вашей карьеры, когда вам надо вникать в существующие проекты без опыта работы. Например, встречая класс с суффиксом Builder, я понимал, что его добавили с целью упрощения и изоляции логики построения сложных объектов. Я сразу легко находил как им пользоваться и применять в своем коде. Повсюду были разбросаны представители шаблона Singleton, совершить ошибку при инициализации которых так легко без знаний правил применения. В коде, с которым я работал, обильно встречались Facade, Visitor, Chain of Responsibility, Iterator, Adapter, Decorator, Proxy, Strategy, Template Method и прочие популярные шаблоны проектирования.
Я осознал, как много времени я экономлю, применяя свои скудные книжные знания шаблонов проектирования и даже в душе зауважал их авторов. Мне было легко не только понимать чужой код, но и расширять его своими решениями, а также добавлять новые.
А как без шаблонов?
Время шло… Я достаточно быстро привык к повсеместному применению шаблонов проектирования и мне стало сложно работать без них. Я начал понимать для чего на собеседовании у кандидатов спрашивают о них (конечно, если не просто «для галочки»). Тут речь даже не об обязательном применении шаблонов проектирования, а об упрощении общения между разработчиками. А это тот процесс, который занимает ключевое место в разработке — обсуждение архитектуры и дизайна конкретного решения задачи.
Первый важный параметр — это время, которое тратится на обсуждение и принятие решения (я надеюсь, что у вас решения принимает не один бородатый Senior Senior Global Product Software Architect). Представьте себе как сложно было бы быстро объяснить кому-то, что нужно реализовать Decorator: «нам нужно сделать класс, которому мы передадим в конструкторе экземпляр другой реализации того же интерфейса и который будет добавлять логику к вызову этих методов, не меняя их основного поведения. » А ведь еще за кадром остались куча мелочей и нюансов. И это для мелкой детали вашего дизайна, которых в большинстве решений десятки, а то и сотни. Мы даже не трогаем сложные и серьезные архитектурные шаблоны.
На примере с Decorator легко понять второй важный параметр — одинаковое понимание дизайна решения задачи в головах всех членов команды. При размытости формулировки каждый может понять решение по-разному, а это чревато проблемами. Ведь реализация может сильно отличаться от обсуждаемой задумки. А это приведет к дополнительному времени на ревью кода и переделки.
Третий важный параметр — понимание работы сторонних инструментов и библиотек. На данный момент практически в каждом проекте используется множество сторонних решений. Чтобы их использовать правильно и не наступать на грабли, архитектор и разработчик должны понимать как что устроено. А для этого используются общеизвестные шаблоны, которые призваны сильно упростить понимание и сравнить с альтернативными решениями.
В жизни мы активно используем примеры для описания ситуаций, предметов, поступков. Чтобы объяснить кому-то какую-то концепцию, мы базируемся на общеизвестных знаниях и выстраиваем примеры на их основе. «Такой же здоровый как Вася», «так же тяжело как после 5 км пробежки», «плохо как с бодуна», «кислый как лимон» и т.д. Подобные выражения мы используем в своей речи постоянно и даже не замечаем этого. Для нас их применение проще чем детальное описание и это позволяет вашему собеседнику лучше вас понять.
Следующий уровень
Если вы заметили, что вы не пытаетесь вспомнить детали реализации шаблона проектирования, а просто можете изложить детали его применения своими словами, то вы переросли уровень Shu в известной восточной философии Shuhari (я когда-то давно писал о ее применимости к Agile подходам и практикам). На уровне Shu вы просто следуете шаблонам и не можете осознать их полезность, тонкости и влияние. На уровне Ha вы уже все осознаете и можете сознательно отказываться от определенных шаблонов, критиковать решения на их базе, видоизменять некоторые шаблоны под конкретную ситуацию и контекст.
На уровне Ha я настоятельно рекомендую прочитать отличную книгу «Refactoring to Patterns» от Джошуа Кериевски. В ней рассказывается о том, как находить в коде неподходящие или плохо примененные шаблоны проектирования, а потом посредством рефакторинга приводить их к верным и подходящим решениям. Эту книгу стоит читать именно на уровне Ha, потому что до этого она будет для вас просто пустым звуком.
У как же уровень Ri? На этом уровне вы и вовсе перестаете задумываться о применении шаблонов. Решения рождаются натурально на базе ваших знаний и навыков, которые вы накопили с годами. Где-то вырисовываются одни шаблоны, где-то ваши собственные наработки, которые стали для вас шаблонами в данном контексте. В голове у вас перестает работать цепочка «от шаблона к решению» и остается только «от решения к шаблону». Тогда вместо вопросов о конкретных шаблонах проектирования на собеседовании вы переходите к открытым вопросам о применимости данного инструмента и примерах из реальной жизни…
Заключение
Шаблоны проектирования — это один из инструментов разработчика, который помогает ему сэкономить время и сделать более качественное решение. Как и любой другой инструмент, в одних руках он может принести много пользы, а в других — один только вред. Я попытался донести на примерах, что конкретно дадут вам шаблоны проектирования и как к ним стоит относиться. Надеюсь, мне это удалось…
Паттерны проектирования для новичков
Внимание! Материал был обновлён и разделён на три части. Предлагаем вам ознакомиться с ним по следующим ссылкам:
Если вы когда-либо интересовались, что представляют собой шаблоны проектирования, то добро пожаловать. В этой статье я расскажу, что это такое, зачем они нужны, как их использовать, и приведу примеры наиболее распространенных шаблонов на PHP.
Что такое шаблоны проектирования
Шаблоны проектирования — это проверенные и готовые к использованию решения часто возникающих в повседневном программировании задач. Это не класс и не библиотека, которую можно подключить к проекту, это нечто большее. Шаблон проектирования, подходящий под задачу, реализуется в каждом конкретном случае. Кроме того, он не зависит от языка программирования. Хороший шаблон легко реализуется в большинстве, если не во всех языках, в зависимости от выразительных средств языка. Следует, однако, помнить, что такой шаблон, будучи примененным неправильно или к неподходящей задаче, может принести немало проблем. Тем не менее, правильно примененный шаблон поможет решить задачу легко и просто.
Существует три типа шаблонов:
Структурные шаблоны определяют отношения между классами и объектами, позволяя им работать совместно.
Порождающие шаблоны предоставляют механизмы инициализации, позволяя создавать объекты удобным способом.
Поведенческие шаблоны используются для того, чтобы упростить взаимодействие между сущностями.
Зачем нужны шаблоны проектирования
Шаблон проектирования, по своей сути, это продуманное решение той или иной задачи. Если вы столкнулись с известной задачей, почему бы не использовать готовое решение, проверенное опытом?
Пример
Давайте представим, что вам необходимо объединить два класса, которые выполняют различные операции в зависимости от ситуации. Эти классы интенсивно используются существующей системой, что не позволяет удалить один из них и добавить его функциональность во второй. Кроме того, изменение кода потребует его тщательного тестирования, поскольку такой рефакторинг ведет к неизбежным ошибкам. Вместо этого вы можете реализовать шаблоны «Стратегия» и «Адаптер» и с их помощью решить задачу.
Просто, не правда ли? Давайте посмотрим поближе на шаблон «Стратегия».
Паттерн проектирования «Стратегия»
Стратегия — поведенческий шаблон, который позволяет выбрать поведение программы в процессе выполнения в зависимости от контекста путем инкапсуляции нескольких алгоритмов в разных классах.
Где это можно использовать
Представьте, что вы разрабатываете класс, который может создать или обновить запись в базе данных. В обоих случаях входные параметры будут одни и те же (имя, адрес, номер телефона и т. п.), но, в зависимости от ситуации, он будет должен использовать различные функции для обновления и создания записи. Можно каждый раз переписывать условие if/else, а можно создать один метод, который будет принимать контекст:
Обычно шаблон «Стратегия» подразумевает инкапсуляцию алгоритмов в классы, но в данном случае это излишне. Помните, что вы не обязаны следовать шаблону слово в слово. Любые варианты допустимы, если они решают задачу и соответствуют концепции.
Шаблон «Адаптер»
Адаптер — структурный шаблон, который позволяет использовать класс, реализующий нужные функции, но имеющий неподходящий интерфейс.
Также он позволяет изменить некоторые входные данные для совместимости с интерфейсом внутреннего класса.
Как его использовать?
Другое название адаптера — «Обертка». Он «оборачивает» новый интерфейс вокруг класса для его использования. Классический пример: вам надо создать класс предметной модели, имея классы объектов в базе данных. Вместо того, чтобы обращаться к табличным классам напрямую и вызывать их методы по одному, вы можете инкапсулировать вызовы этих методов в одном методе в адаптере. Это не только позволит повторно использовать набор операций, но и избавит вас от постоянного переписывания большого количества кода, если вам потребуется выполнить тот же набор действий в другом месте.
Сравните два примера.
Без адаптера:
Если нам придется использовать такой код повторно, мы будем вынуждены переписывать все это заново.
С использованием адаптера:
Мы можем создать класс-обертку Account :
Теперь мы можем использовать класс Account каждый раз и, кроме того, мы можем добавить в него дополнительные функции.
Шаблон «Метод-фабрика»
Фабрика — порождающий шаблон, который представляет собой класс с методом для создания различных объектов.
Основная цель этого шаблона — инкапсулировать процедуру создания различных классов в одной функции, которая в зависимости от переданного ей контекста возвращает необходимый объект.
Как его использовать?
Сначала создадим три класса:
Теперь мы можем написать нашу фабрику:
На выходе должен получиться HTML со всеми типами кнопок. Таким образом мы получили возможность указать, кнопку какого типа мы хотим получить, и использовать код повторно.
Шаблон «Декоратор»
Декоратор — это структурный шаблон, который позволяет добавить новое поведение объекту в процессе выполнения программы в зависимости от ситуации.
Цель — в расширении поведения конкретного объекта без необходимости изменять поведение базового класса. Это позволит использовать несколько декораторов одновременно. Этот шаблон — альтернатива наследованию. В отличие от наследования, декоратор добавляет поведение в процессе выполнения программы.
Для реализации декоратора нам понадобится:
Как его использовать?
Предположим, что у нас есть объект, который должен иметь определенное поведение в определенной ситуации. Например, у нас есть HTML-ссылка для выхода из аккаунта, которая должна по-разному показываться в зависимости от того, на какой странице мы находимся. Это тот самый случай, когда нам помогут декораторы.
Сначала определимся, какие «декорации» нам нужны:
Теперь мы можем написать сами декораторы:
Теперь мы можем использовать их так:
Обратите внимание, как можно использовать несколько декораторов на одном объекте. Все они используют функцию __call для вызова оригинального метода. Если мы войдем в аккаунт и перейдем на заглавную страницу, результат будет такой:
Шаблон «Одиночка»
Одиночка — порождающий шаблон, который позволяет убедиться, что в процессе выполнения программы создается только один экземпляр класса с глобальным доступом.
Его можно использовать как точку «координации» для других объектов, поскольку поля «Одиночки» будут одинаковы для всех, кто его вызывает.
Как его использовать?
Теперь мы можем получить доступ к сессии из различных участков кода, даже из других классов. Метод getInstance всегда будет возвращать одну и ту же сессию.
Заключение
В этой статье мы рассмотрели только наиболее часто встречающиеся шаблоны из множества. Если вы хотите узнать больше о шаблонах проектирования, вы найдете достаточно информации на Википедии. Для более полной информации обратите внимание на знаменитую книгу «Приемы объектно-ориентированного проектирования» «Банды четырех».
И последнее: при использовании того или иного шаблона убедитесь, что вы решаете задачу правильным способом. Как уже упоминалось, при неправильном использовании шаблоны проектирования могут доставить больше проблем, чем решить. Но при правильном — их пользу нельзя переоценить.
Зачем нужны паттерны проектирования или «Что такое MVC?»
Самое главное во всех фреймворках это то, что все они диктуют правила создания приложения. Если ты никогда не использовал никакого фреймворка в своих приложениях, то либо они слишком малы и ты не сталкивался с проблемой нарастающего хаоса в коде, либо просто не пришло твоё время 🙂 И в конце концов приложение созданное по правилам фреймворка однозначно обретёт правильную форму. А разве не этого все мы хотим? Новичок в программировании начав изучение какого-либо фреймворка автоматически начинает правильно мыслить, тем самым перенимая опыт предыдущих поколений и открывая себе более гладкую дорогу вперёд. В любом случае единственная возможность создания больших, расширяемых и модульных приложений это использование фреймворка.
MVC «Модель-представление-поведение» === «Модель-представление-контроллер».
А теперь закрой глаза и представь себе работу своего приложения. Представил?… так вот, попробуй условно разбить этот процесс на три абстрактые логические части:
Собери все методы своего приложения которые занимаются обработкой всех визуальных компонентов, методы которые имеют знания о визуальном элементе, которые занимаются перемещением их по экрану и т.д. Мысленно объедини их в одну часть программы, это и будет (Представление).
Далее. Собери все методы своего приложения которые занимаются логикой приложения, методы которые имеют знания о том, что и как делать. Модель поведения компонентов приложения. Мысленно объедини их в одну часть программы, это и будет (Модель).
Третья, но не менее важная часть, это те методы в которых реализовано само поведение модели. Так называемый «Контроллер». Вообщем это любые методы, которые вызываются к примеру событием проигрыша игры, допустим: function onLoseLevel(e:SomeEvent):void. В данном случае этот метод является поведением модели вашего приложения в случае проигрыша. Как только вы проиграли уровень вызывается конкретный метод, которые реализует поведение приложения в конкретном случае. Так вот, собери все методы, реализующие поведение в одну кучу
это и будет третья логическая часть концепции MVC, (Контроллер).
Для тех кто не до конца понял и не ответил на вопрос для чего нужно это все разбиение: Для того, чтобы уменьшить связываемость кода, т.е. меняя, что-то в проекте изменения не затронут другой части программы. Таким образом наше приложение может быть более гибким и состоять из независимых модулей и приспособлено к рефакторингу.
Реализация концепции MVC на примере приложения HellowWorld
Ок. Разберём что же здесь происходит. Есть Главный класс приложения: HelloWorld.as который ответственный за всё. В нём мы создаём один единственный визуальный компонент типа TextField (текстовое поле), а также экземпляры Model.as, View.as и Controller.as. Теперь как это всё связать?
Для начала передадим визуальный компонент в управление нашему View путём передачи его в конструктор:
artemfedorov.com/?p=64
Теперь наша View знает о компоненте.
Далее, добавим метод во View с помощью которого она сможет влиять на компонент:
здесь всего лишь происходит присвоение текстовому полю компонента нового значения типа String.
Наша Model в этом примере содержит лишь знания о значении строковой переменной и на это бизнес логика ограничивается.
Controller содержит тоже лишь одну команду, которая получает значение из Model и сообщает View что нужно сделать:
Далее заключительный штрих без которого ничего не будет работать это событие. Здесь для примера события я выбрал событие, которое возникает когда происходит добавление на stage и добавил слушатель:
addEventListener(Event.ADDED_TO_STAGE, showTextHandler);
К этому событию я привязал команду реализуя поведение моего приложения:
Паттерны проектирования: твоя настольная статья
Паттерны проектирования делятся на три группы.
1. Порождающие паттерны
1. Абстрактная фабрика помогает создавать семейства объектов с общими связями или зависимостями без указания их конкретных классов. Фабрики и продукты – основа паттерна. Как раз слово «семейства» отличает шаблон от других порождающих паттернов, где только один тип объекта.
2. Строитель отделяет конструирование сложного объекта от его представления. Представьте приготовление чая с сахаром, чая с молоком и обыкновенного чая. Вы одинаково кипятите воду и насыпаете заварку, а в зависимости от типа добавляете сахар, вливаете молоко или ничего не делаете.
3. Фабричный метод определяет интерфейс для конструирования объекта и даёт свободу дочерним классам выбирать тип класса.
Паттерн делегирует создание экземпляров подклассам. Создадим полосу прокрутки в стиле Mac:
А чтобы это работало на любой платформе, напишем код:
Переменную экземпляра guiFactory инициализируем так:
4. Прототип определяет тип объектов с применением прототипического экземпляра и создаёт новые путём его копирования.
5. Одиночка гарантирует, что у класса будет единственный экземпляр, и предоставляет глобальную точку доступа к нему.
2. Структурные паттерны
1. Адаптер конвертирует интерфейс класса в ожидаемый клиентами интерфейс. Благодаря этому классы работают вместе, несмотря на несовместимость интерфейсов.
2. Мост отделяет абстракцию объекта от его реализации.
3. Компоновщик объединяет объекты в структуры в виде дерева для представления иерархий частей и целого. Поэтому клиенты одинаково работают и с одним объектом, и с композицией объектов.
4. Декоратор добавляет обязанности к объектам динамически. Это гибкая альтернатива дочерним классам для расширения функциональности.
5. Фасад – единственный класс, представляющий целую подсистему.
6. Приспособленец – мелкоструктурный экземпляр для облегчения и оптимизации совместного доступа.
7. Заместитель изображает и представляет другой объект.
3. Поведенческие паттерны
1. Посредник определяет упрощённую связь между классами.
2. Хранитель фиксирует и восстанавливает внутреннее состояние объекта.
3. Интерпретатор – способ добавления языковых элементов в программу.
4. Итератор – последовательный доступ к элементам коллекции.
5. Цепочка обязанностей – способ передачи запросов между цепочкой объектов.
6. Команда упаковывает запрос в объект для параметризации отличающихся клиентов, создания очереди, логирования или поддержки операции отмены.
7. Состояние трансформирует поведение объекта, когда он меняет своё состояние.
8. Стратегия определяет семейство алгоритмов, инкапсулирует каждый для взаимозаменяемости. Благодаря чему изменение алгоритма не подвергается влиянию клиентов, которые его используют.
Пример: Утиный класс с инкапсулированным поведением, таким как Flybehavior для полёта и Quackbehavior для кряканья.
9. Наблюдатель устанавливает объектную зависимость «один ко многим» следующим образом: когда состояние главного объекта изменяется, зависимые уведомляются и получают автоматическое обновление.
10. Шаблонный метод задаёт каркас алгоритма операции, делегируя некоторые шаги дочерним классам. Дочерние классы переопределяют конкретные этапы алгоритма и оставляют его структуру неизменённой.
11. Посетитель добавляет новую функциональность классу без изменений.
Композиция против агрегации
Агрегация и композиция – сильные формы ассоциации, которые описывают отношения между целым и его частями. Таким образом, вместо отношения «имеет», как у обыкновенной ассоциации, получаем отношение «это часть» или в обратном направлении – «состоит из».
Примеры такого вида связей:
Композиция ещё сильнее агрегации. Для проверки типа связи используйте правило единственного использования для композиции: часть принадлежит только одному целому.
Для примера №1 и №2 музыкант или продукт принадлежит только одному оркестру или каталогу? Да! Но может ли в примере №3 комната принадлежать двум зданиям? Нет! Сработало правило единственного использования. Таким образом, отношение №3 лучше описывается композицией, чем агрегацией.
Вот другое правило для композиции: если целое уничтожено, уничтожается ли его часть?
Только для случая №3 работает дополнительное правило композиции. Если оркестр распадётся, исчезнут ли музыканты? Нет!
Пример кода: в университете масса факультетов, в каждом из которых группа профессоров. Если университет закроется, факультетов больше не будет, но профессора останутся. Следовательно, университет – композиция факультетов, а у факультетов агрегация профессоров. Кроме того, профессор может работать не только на одном факультете, но а факультет не может быть частью двух университетов.
1. Университет – факультет: композиция: собственность: уничтожаются вместе: сильное отношение «имеет».
Составной объект – владелец компонента и отвечает за создание и уничтожение частей. Компонент – часть одного составного объекта, уничтожение которого ведёт к ликвидации частей.
Композиция гарантирует инкапсуляцию, поскольку части – члены составного объекта:
2. Факультет – профессора: агрегация: нет собственности: отдельное существование: у компонента остаётся слабое отношение «имеет».
В агрегации объект содержит только ссылку или указатель на объект. Объект не отвечает за ссылку или указатель на протяжении всего жизненного цикла:
Полный пример кода и диаграмма:
В UML композиция изображается в виде закрашенного ромба и сплошной линии. Это говорит о кратности 1 или 0. 1, то есть ответственность за объект несёт только один объект. Агрегация обозначается в виде пустого ромба и сплошной линии.
Наследование против композиции
Оба способа применяют для многократного использования функциональности.
Наследование
Композиция
С помощью наследования реализуют один класс на базе другого. В альтернативной композиции новая функциональность получается при составлении объекта. В результате получаем сокрытие внутренних деталей объектов, чего нет в наследовании.
Минусы наследования
Хотя наследование классов облегчает модификацию, реализация подкласса становится слишком связанной с родительской. В результате любая правка в реализации родителя затрагивает подкласс.
Посмотрите на пример:
Однако, если изменить метод makeSound() родительского класса, вот так:
Как насчёт композиции
При использовании композиции подкласс становится front-end классом, а суперкласс – back-end классом. При наследовании подкласс автоматически наследует реализацию любого неприватного метода суперкласса, если не переопределяет его. С композицией, однако, front-end класс явно вызывает соответствующий метод back-end класса из собственной реализации метода. Этот явный вызов иногда называется переадресацией или делегированием вызова метода back-end объекту.
Композиция даёт больше инкапсуляции, чем наследование, потому что изменение back-end класса не нарушает код, полагающийся на front-end класс. При наследовании подкласс знает детали реализации родителя и своеобразно вредит инкапсуляции.
Пример показывает, что цепная реакция от изменения back-end класса останавливается на front-end классе.
Композиция помогает сохранять каждый класс инкапсулированным и ориентированным на одну задачу. Классы и иерархии классов останутся маленькими и не разрастутся до неуправляемости.
Однако в проекте на базе композиции большее количество объектов (при уменьшении классов) и глобальное поведение опирается на их взаимосвязи, а не объявляется в одном классе.
Чаще используйте композицию.
Делегирование
Делает использование композиции для переиспользования равным по мощности наследованию. В делегировании два объекта участвуют в обработке запроса. Принимающий объект поручает действия делегату, что похоже на случай, когда подкласс доверяет запросы родительскому классу.
Основное преимущество делегирования в лёгкости объединения поведения во время выполнения. Например, превратим Window в круг рантайм:
Выбирайте делегирование, только когда больше упрощения, чем усложнения. Динамическое и параметризованное программное обеспечение сложнее в понимании, чем статичное, и добавляется проблема неэффективности во время выполнения. Поэтому используйте делегирование с умом, например, следуя стандартным паттернам проектирования, таким как Стратегия.