В процессе разработки программного обеспечения очень часто встречаются одни и те же проблемы и задачи, очень часто они даже не зависят от того, какой конкретно язык программирования используется. Для таких проблем существуют известные способы решения, хорошо зарекомендовавшие и ставшие обычными. Такие решения и принято называть паттернами программирования (шаблонами проектирования и т.п.). Паттерн программирования - это независимая от языка стратегия решения проблем при разработке средствами ООП. Если вы разработчик, то вам следует знать названия распространенных шаблонов. Изучение паттернов помогает обмениваться информацией с другими разработчиками более эффективно за счет использования общих терминов. В действительности, вы скорее всего уже знакомы с некоторыми шаблонами, просто, возможно, не используете общеизвестные названия для этих приемов.
Ну, если вы хотите стать профессиональным Java-разработчиком, то вам следует знать по крайней мере популярные решения встречающихся проблем. Эти решения были отточены и улучшены опытными программистами. Как только вы освоите их, вы начнете получать от них пользу и сделаете огромный скачок на пути становления мастером проектирования и разработки. Более того, вы сможете использовать эти термины для более эффективного общения с вашими коллегами.
Многие программисты с многолетним опытом работы не знают паттерны программирования, но как ООП-программист, вы должны их хорошо знать, особенно это касается новоиспеченных Java-программистов. Вообще-то, когда вы решаете задачу программирования, вы пользуетесь паттерном проектирования. И если вы не знаете паттернов, то не можете использовать понятное другим название того приема, которым воспользовались, чтобы описать его, вы также не можете выбрать более эффективный путь для того, чтобы проконтролировать то, что создали. Изучение того, как опытные программисты решают задачи программирования и попытки использовать эти знания в вашем проекте - это лучший путь для получения нового опыта и сертификации.
Помните - изучение паттернов изменит ваш стиль написания кода; вы не только будете образованнее, вы также будете выглядеть более образованными.
Много. Всего насчитывается по крайней мере 250 паттернов, используемых в мире ООП, включая Спагетти, который относится к небольшим привычкам. Широко известны 23 паттерна программирования, и еще больше станет известно по пути.
Заметьте, что паттерны - это не идиомы и алгоритмы и не компоненты.
Вообще, чтобы построить систему, вам может понадобиться совместить много паттернов вместе. Разные разработчики могут решать одну и ту же проблему используя разные паттерны. Обычно:
Материал статьи для уровня Beginners. Здесь не будет Moose, только чистый Perl. Предполагается, что какое-то ООП в Perl уже знакомо
Паттерны это стандартные приемы, решающие небольшую конкретную задачу. Это не инструкция, как писать код, а схема или принцип организации кода, модулей и т. п. Уверена, что если вы их не знаете на уровне диаграмм UML, то встречали в коде. Этот небольшой обзор познакомит с самыми простыми, полезными и часто используемыми паттернами.
Порождающий паттерн. Используется в случае, когда в системе должен быть только один экземпляр какого-то класса. Например, подключение к базе, распарсенный файл конфигурации и т. д. Но при этом вы не хотите таскать с собой какие-то глобальные переменные. Невероятно удобен для отложенных инициализаций тех же конфигов.
Пусть у нас будет какой-то абстрактный класс с именем MyClass .
Package MyClass; use strict; our $singleton = undef; sub new { my $class = shift; return $singleton if defined $singleton; my $self = {}; $singleton = bless($self, $class); $singleton->init(); return $singleton; } # other methods sub init { #... } 1;
$singleton->init(); - вот тут, к примеру, проводится какая-то инициализация (либо она может быть отложена до вызова конкретных функций).
На выходе
Bob Bob Mike Mike
В результате вызова функций f() и f2() мы получим один и тот же созданный объект, ссылка на который хранится у нас в $MyClass::singleton , с ней можно работать напрямую, но это моветон и делать так не надо (за исключением ситуаций, когда требуется высокая производительность, а использование аксессоров создаёт ощутимые накладные расходы).
Таким образом, можно в любом месте кода создавать объект через конструктор и не волноваться, что он каждый раз будет создаваться заново.
На CPAN, кстати, есть Class::Singleton , MooseX::Singleton , Apache::Singleton и еще куча других.
Порождающий паттерн. Берет на себя ответственность за создание объекта нужного класса. Мы просто обращаемся к ее конструктору, а какой нам вернуть объект, фабрика решает сама. Создаваемые объекты, конечно, должны быть из одного семейства и иметь идентичный интерфейс. То есть, они должны быть взаимозаменяемыми.
В качестве примеров использования: в номере 21 в статье паттерн использован для создания объекта-логгера в зависимости от способа вывода: либо stderr, либо file. В более бизнесовом мире встречаются разные способы доставки (там все одинаковое, но разные формочки, разные коэффициенты какие-нибудь), разные форматы прайсов от поставщиков (у кого-то Excel, у кого-то XML), разные способы отправки уведомлений (e-mail, SMS).
У меня будет пример очень абстрактный, но очень понятный. Допустим, у нас есть ферма с животными. Нам, с точки зрения логики, все равно, какое животное будет создано, мы только задаем в параметрах, сколько у него ног. (В реальности значение количества ног мы получаем из внешнего конфига, а не задаем в коде).
На выходе
Chicken Cow
Тут важно понимать, что обращаясь к конструктору AnimalFactory, мы получаем объект класса вовсе не AnimalFactory, а того, который она решит создать.
Если нам понадобится класс Snake , то мы просто добавим логику его создания в AnimalFactory , как-нибудь так:
Return Snake->new() if $opt->{legs} == 0;
Если вдруг Cow нужно будет заменить на Horse , это нужно будет сделать только в одном месте - в AnimalFactory , не затрагивая других участков кода.
Абстрактную фабрику стоит использовать там, где класс объекта зависит от каких-нибудь внешних факторов: пользовательских настроек, версии браузера, ОС и т. п.
(В некоторых случаях не очень хорошо, что мы подгружаем все возможные классы сразу через use , это можно изменить: внести внутрь конструктора и подключать классы через require уже после анализа параметров и до создания конкретного объекта.)
Паттерн поведения. Паттерн используется для определения основного алгоритма для всех подклассов. Берем алгоритм, делим его на много мелких этапов, пишем в базовом классе, а все подклассы реализуют различающиеся части.
Самый простой пример: импорт товаров от поставщика. Нужно распарсить файл, пройти по всем товарам от поставщика, если товар найден - обновить его, если не найден - создать, подсчитать конечную стоимость, записать операцию с товаром в журнал, проделать что-нибудь еще с чем-нибудь.
(Здесь я использую фабрику для создания нужного мне объекта по имени поставщика, от которого загружается файл.)
Но можно обойтись и без фабрики, а сделать вот так (хотя гибкость это явно снижает, но она и не всегда такая нужна):
My $type = "Bekka"; my $import = $type->new(); $import->do;
Допустим, у меня тут два поставщика: Bekka
Package Bekka; use base "Import"; sub parse { # parse Excel } sub count_price { # price * 2 } 1;
который присылает файлы в Excel, и у которого цену из файла нужно увеличивать в два раза.
И Pukka , у которого файлы в XML, а цену нужно делить пополам:
Package Pukka; use base "Import"; sub parse { # parse XML } sub count_price { # price / 2 } 1;
Оба эти класса имеют родителя Import , который и описывает основной алгоритм загрузки файла (sub do). В нем определяются все используемые методы, но работающие по какому-то умолчанию. (У методов, конечно, еще есть какой-нибудь код, но здесь он не нужен, поэтому его не привожу.)
Package Import; ... sub do { my $self = shift; $self->parse(); while ($self->next) { if ($self->find) { $self->update; } else { $self->insert; } $self->count_price; $self->log; } $self->finish; } sub next; sub find; sub update; sub insert; sub count_price { my $self = shift; # use original price } 1;
Получается: фабрика создает нам объект нужного класса, основываясь на имени поставщика. Базовый объект для него описывает весь процесс импорта товара от любого поставщика. Объект конкретного класса переопределяет те методы, которые ему не подходят, на свою реализацию - в нашем случае методы count_price и parse .
Метод do из класса Import и есть наш шаблонный метод - он описывает шаблон поведения. И вовсе необязательно, что он должен его реализовывать. В реальности сложно найти задачи такого плана, которые могут быть удовлетворены поведением по умолчанию.
Удобно использовать констукцию can для методов, которые не обязательно должны быть в базовом классе, но могут быть в подклассах: $self->do_smth if $self->can("do_smth") , тогда метод будет вызваться только в том случае, если он реально определен. Это избавит от кучи пустого кода, а также позволяет писать довольно удобно хуки, типа:
$self->before_update() if $self->can("before_update"); $self->update(); $self->after_update() if $self->can("after_update");
Паттерн поведения. Другое название - Политика. Используется для взаимозаменяемости алгоритмов или их фрагментов. Например, когда у нас есть разные способы расчета скидки на заказ. (Пример высосан из пальца, и для таких случаев делать подобные схемы - роскошь. Но он прост и понятен.)
На выходе
Класс Заказ
Package Order; sub new { return bless {}, shift } sub get_summa { my $self = shift; my $opt = {@_}; my $summa = $opt->{discounter}->do(summa => $self->{ summa }); return $summa; } 1;
Фабрика DiscountFactory (ее кода здесь нет, там все как и в обычной фабрике) возвращает объекты класса либо DiscountVisa , либо DiscountYM:
Package DiscountVisa; sub new { return bless {}, shift } sub do { my $self = shift; my $opt = {@_}; # Здесь я позволила себе использовать # «магическое число» --- это только для наглядности # примера. Так делать плохо. return $opt->{summa} * (1 - 0.02); } package DiscountYM; sub new { return bless {}, shift } sub do { my $self = shift; my $opt = {@_}; return $opt->{summa} * (1 + 0.05); } 1;
В классе Order у нас есть метод get_summa , который возвращает конечную стоимость заказа, но он должен учитывать и скидку на заказ. А скидка на заказ определяется способом оплаты заказа.
my $discounter = DiscountFactory->new(type => "Visa") - создали наш объект-дискаунтер, который знает, как считать скидку при оплате картой Visa.
$order->get_summa(discounter => $discounter) - вызываем метод для получения итоговой стоимости заказа, передавая туда нашу «стратегию» расчета скидки.
my $summa = $opt->{discounter}->do(summa => $self->{ summa }); - в методе get_summa мы вызываем операцию применения скидки к нашей базовой стоимости заказа.
То есть, мы передаем стратегию расчета скидки для заказа в качестве параметра. Эту же стратегию мы можем в дальнейшем использовать и в других функциях, работающих со стоимостью заказа, заменять ее, не меняя остальной код.
На деле все очень просто, в следующей статье обязательно расскажу про другие очень используемые паттерны с чуть более сложной реализацией.
←Главная польза каждого отдельного шаблона состоит в том, что он описывает решение целого класса абстрактных проблем. Также тот факт, что каждый шаблон имеет свое имя, облегчает дискуссию об абстрактных структурах данных (ADT) между разработчиками, так как они могут ссылаться на известные шаблоны. Таким образом, за счёт шаблонов производится унификация терминологии, названий модулей и элементов проекта.
Правильно сформулированный паттерн проектирования позволяет, отыскав удачное решение, пользоваться им снова и снова.
В отличие от идиом, шаблоны независимы от применяемого языка программирования.
Иногда шаблоны консервируют громоздкую и малоэффективную систему понятий, разработанную узкой группой. Когда количество шаблонов возрастает, превышая критическую сложность, исполнители начинают игнорировать шаблоны и всю систему, с ними связанную.
Нередко шаблонами заменяется отсутствие или недостаточность документации в сложной программной среде.
Есть мнение, что слепое применение шаблонов из справочника, без осмысления причин и предпосылок выделения каждого отдельного шаблона, замедляет профессиональный рост программиста, так как подменяет творческую работу механическим подставлением шаблонов. Люди, придерживающиеся данного мнения, считают, что знакомиться со списками шаблонов надо тогда, когда «дорос» до них в профессиональном плане - и не раньше. Хороший критерий нужной степени профессионализма - выделение шаблонов самостоятельно, на основании собственного опыта. При этом, разумеется, знакомство с теорией, связанной с шаблонами, полезно на любом уровне профессионализма и направляет развитие программиста в правильную сторону. Сомнению подвергается только использование шаблонов «по справочнику».
Шаблоны могут пропагандировать плохие стили разработки приложений, и зачастую слепо применяются.
Для преодоления этих недостатков используется рефакторинг .
Также на сегодняшний день существует ряд других шаблонов:
Wikimedia Foundation . 2010 .
У этого термина существуют и другие значения, см. Паттерн. В разработке программного обеспечения, шаблон проектирования или паттерн (англ. design pattern) повторимая архитектурная конструкция, представляющая собой решение проблемы… … Википедия
- (паттерн, англ. design pattern) это многократно применяемая архитектурная конструкция, предоставляющая решение общей проблемы проектирования в рамках конкретного контекста и описывающая значимость этого решения. Паттерн не является законченным… … Википедия
GRASP (англ. General Responsibility Assignment Software Patterns (общие образцы распределения обязанностей)) паттерны, используемые в объектно ориентированном проектировании для решения общих задач по назначению обязанностей классам и объектам. В … Википедия
По своей сути - паттерны - это обычные шаблоны проектирования. Заимствовано у обычных архитекторов (те, которые зданиями занимаются). Суть проста. В работе архитектора есть задачи, которые удобно решать одним или несколькими проверенными способами.
По аналогии в проектировании софта имееются свои архитектурные вопросы вроде разбиения приложения на компоненты/модули, организации зависимостей между ними, распределение функциональных обязанностей и т.п. Как ловко подметили авторы книжки из этой банды четырех (The "Gang of Four") в нашей индустрии можно также выделить некоторе количество типовых шаблонов, проверенных на практике, чтобы тем самым не наступать на уже обойденные другими грабли.
Суть постижения паттернов заключается в том, чтобы осознать в каких ситуациях правильно использовать тот или иной шаблон проектирования и правильно его применить. Важно понимать при этом, что формула "чем больше паттернов я придумал засунуть с свое приложение - тем лучше" - неверная. Использовать их следует с умом и только там, где они действительно нужны. Кроме того, патерны устаревают, превращаются в анти-паттерны по мере развития технологий (которые в нашей области делают это более чем стремительно). Ну и, конечно, есть шаблоны общепринятые и есть те, которые успешно используются в узких кругах.
Тут тоже надо понимать, что это не догма какая-то - типа 10 священных паттернов проектирования:)
Чтобы понять, где они нужны - нужен опыт. То есть (я лично убежден), что учиться на ошибках других может только крайне ограниченное число людей. Все остальные обязаны набить шишки самостоятельно:)
К изучению паттернов я дам такие советы:
1) Прочтите пару книжек, чтобы понять, что это за зверь и с чем его едят. Можно взять одну из вариаций книжки GoF или какие-то производные для вашего стека разработки - познакомиться с основными популярными шаблонами. Сразу после этого я посоветовал бы прочесть книжку "Горький вкус Java" (Брюс Тейт) - она про анти-паттерны. Это чтобы понять обратную сторону их использования. Мне понравилась и уберегла думаю от многих проблем. То что на примере Java - неважно. Речь идет о шаблонах, так что представителям других стеков (к которым отношусь и я) будет просто понять все равно.
2) Постарайтесь осознать, доводилось ли вам сталкиваться в работе раньше с чем-то, что является или могло бы легко стать одним из шаблонов. Где получалось применить концепт верно, а где из-за этого только проблемы были.
3) В новых проектах, держите в голове полученные по шаблонам знания - вдруг пригодятся.
В конечном итоге, знаете ли вы паттерны, или нет - с опытом приходит понимание того, какая архитектура будет правильная, а какая - нет. Как сделать удобно, а как нет. И неважно, какими паттернами это обозвать.
Я даже пожалуй посоветовал бы подойти к освоению айтишной архитектурной мудрости с другой стороны - со стороны нефункциональных требований или так называемых "-ilities" - их много. вот описаны 7 штук. А вообще их десятки .
Среди прочих - такие как maintainability (простая поддержка кода), scalability (масштабируемость), extensibility (расширяемость), availability (устойчивость) и тп. Если, проектируя свое приложение, вы задумываетесь об этих "илитях" и стараетесь их обеспечить в необходимом проекту объеме, то, как правило, ваше приложение будет иметь отличную архитектуру. При этом шаблоны проектирования в ней появятся лаконично сами собой.
Поскольку идея использовать шаблоны - это попытка опытных программных инженеров дать десяток готовых рецептов менее опытным, чтобы пока они не научились варить "вкусную кашу", они не варили уж что-то совсем несъедобное. :) Учитесь "готовить", разбирайтесь в -ilites:) и все будет хорошо