Приветствие
Всем привет! В этот радостный и, достаточно, теплый пятничный денек приключилась у меня (процитирую в более приятном варианте) «рука-лицо». Честно говоря, приключается сие действие достаточно часто, но, по обыкновению, вызывается оно от ощущения:
Боже, какой же я тупой.
В этот раз меня посетило несколько другое чувство, и, как я убедился, не только меня. Чувство это напомнило мне об одном из моих собеседований, где меня попросили написать скелет паттерна «Декоратор», который оказался в личном представлении интервьювера совершенно другим, нежели его классическое толкование.
Не подглядывать
Зная, что в электронной кладези знаний стопроцентно имеется статья «Шаблон проектирования Стратегия», с примером на C++, который скорее всего взят из книжки GoF, и на всех известных языках программирования (а вдруг и включая эзотерические), моей задачей стало: не подглядывая, упихать схожий пример из статьи в концепцию паттерна. Поглядим, что нужно сделать и что получилось.
Пилите, Шура, пилите
На мой взгляд, оперировать конкретными типами классов, не есть хорошо: в данном случа мы обладаем не только знанием о интерфейсе класса, но и о реализации, включаещей обычно ворох дополнительных методов (часто открытых === pulic). Вместо этого, предлагается подход, основанный на интерфейсах, так что первым шагом рефакторинга примера будет выделение оных:
interface IValidator { /** * @param Mixed|stdClass $validatorParam * @return Boolean */ function isValid($validatorParam); }
Я позволил себе немного расслабиться и поэтому код будет содержать минимальное количество комментариев (на самом деле код содержит минимально допустимое, в моей IDE, количество комментариев). Итого: сущность, которая способна выдать вердикт о валидности по переданному аргументу. Стоит написать, что в данном месте, как и вас, меня начинает передергивать:
- Количество аргументов строго фиксируется (переменная массив не наш метод)
- Проблему выше не решить перегрузкой функции (да, C++ смотрит на PHP как на…)
- Тип аргумента не определен (хотя для многих программистов PHP это не проблема)
Но я выдыхаю (просто воздух), смиряюсь с ограничениями и продолжаю писать. Исходя из названия паттерна у нас должен быть некоторый механизм (или возможность) применять разные стратегии в различных случаях ( или вариантах использования), например зависящих от внешних факторов, причем не только на этапе компиляции, но и… ой, не тот язык. В формализованном виде, наше желание будет выглядеть как-то так:
function Validate(IValidator $validateStrategy, $param) { return $validateStrategy->isValid($param); }
А адаптация нашего желания для валидации, например, имени пользователя будет выглядеть следующим образом:
function ValidateName($userName) { return Validate(new NameValidator(), $param); }
«Согласен», отвечу я тебе, внимательный читатель, на твой незримый вопрос:
Каждый раз вызывается конструктор? Может синглтон или статичная переменная функции, не?
Но это уже вопросы следующего рефакторинга. Вернемся к предметной области примера: нам понадобится сущности для валидации, к примеру, почтового ящика (email), и отвечающие на вопросы: этот адрес Hoho? этот адрес не слишком короткий или длинный?
class HohoEmailValidator implements IValidator { public function isValid(/*String*/ $email) { return $email === 'Hoho'; // best comparison of ever } } class LengthEmailValidator implements IValidator { public function isValid(/*String*/ $email) { return strlen($email) > 5; } } class LengthMaxEmailValidator implements IValidator { public function isValid(/*String*/ $email) { return strlen($email) <= 100500; } }
Так, как наша функция Validate() не позволяет использовать коллекцию (список, массив) валидаторов, то придется расширить её функционал использованием коллекции (списка, массива) валидаторов как аргумента:
function IsValidByStrategy(/*Collection of IValidators*/ $strategyCollection, /*Any type*/ $param) { foreach($strategyCollection as $strategy) { if($strategy instanceof IValidator) { // в наше время доверять нельзя никому -_- if(!$strategy->isValid($param)) return false; } } return true; }
О да, это приятное шевеление волос на голове:
- Не отслеживается состояние, когда коллекция валидаторов пуста
- Нельзя изменить поведение функции: она вываливается при первом false функции isValid()
Теперь, когда мы имеем все карты на руках, мы можем описать первую, более менее полезную функцию в контексте паттерна стратегия, которая проверяет переданный ей почтовый адрес:
// Возврашает true, если function IsValidEmailStrong($email = 'habr@habr.ru') { return IsValidByStrategy(array( new HohoEmailValidator(), // это "нормальный" адрес new LengthEmailValidator() // и он больше 5 символов ), $email); }
Логика проверки получилась щикарная (< — это не ошибка):
- Email должен являться строкой ‘Hoho’ — что уже бредово
- Ну, и даже если адрес будет действительно ‘Hoho’, то он никогда не будет больше 5 символов
- Но, следует отметить, что проверка с помощью LengthEmailValidator в данном контексте никогда не будет выполнена
Дополнение
Чтобы сделать пример менее идиотским (помним про LengthEmailValidator), еще разок применим паттерн стртегия, но применительно к изменению поведения функции IsValidByStrategy() и реализуем следующий функционал:
interface IsValidReturnRule { /** * @param Boolean $validateResult * @param Boolean $validateResultByStep * @return Boolean */ function validateStep(&$validateResult, $validateResultByStep); /** * @return Boolean */ function initialize(); } class ValidReturnRuleAny implements IsValidReturnRule { public function validateStep(&$validateResult, $validateResultByStep) { $validateResult |= $validateResultByStep; return $validateResult; } public function initialize() { return false; } } function IsValidByStrategyByRetRule(/*Collection of IValidators*/ $strategyCollection, /*Any type*/ $param, IsValidReturnRule $retStrategy) { $result = $retStrategy->initialize(); foreach($strategyCollection as $strategy) { if($strategy instanceof IValidator) { if($retStrategy->validateStep($result, $strategy->isValid($param))) return $result; } } return $result; }
Итого мы получили:
- Возможность изменения поведения при валидации
- Возможность изменения поведения процесса самой валидации
Теперь, для того, чтобы проверить, что любой почтовый адрес будет считаться валидным нам потребуется написать следующий код:
class ValidReturnRuleAny implements IsValidReturnRule { public function validateStep(&$validateResult, $validateResultByStep) { $validateResult |= $validateResultByStep; return $validateResult; } public function initialize() { return false; } } function IsValidEmailSoft($email = 'habr@habr.ru') { return IsValidByStrategyByRetRule(array( new HohoEmailValidator(), new LengthEmailValidator() ), $email, new ValidReturnRuleAny()); }
Из-за использования стратегии обработки результата стратегии валидации, функция IsValidEmailSoft будет не возражать о любых адресах, имеющих длину не более 100500 символов, т.е.
- IsValidEmailStrong()
- Адрес невалиден, если любая из стратегий валидации вернула false
- IsValidEmailSoft()
- Адрес валиден, если любая из стратегий валидации вернула true
Что нужно помнить
Любое проектное решение реализующее изящное решение (включая особенности языка) общей задачи, имеет ограничения использования. В этом смысле прекрасно стукрурирована книжка Design patterns авторством GoF.
Заключение
Боюсь, эта статья будет образчиком, как, о казалось бы, простых вещах, можно «налить столько воды». Но в одном я уверен: будет здорово, если после этой статьи те из нас, кто по какой-либо причине пренебрегал или боялся познакомиться с паттернами проектирования, победит свой страх.
В конце концов: знание лучше неведения, а свет лучше темноты. Спасибо и удачных всем выходных!
P.S. Да, это моя первая статья, так что: поздравления, критика, оскорбления, ошибки\очепятки принимаются в личку.
ссылка на оригинал статьи http://habrahabr.ru/post/176505/
Добавить комментарий