Pull to refresh

Comments 15

Спасибо.
Приятно что PHP и ZF продолжает развиваться, и радовать нас новыми вкусняхами.
>… это Принцип подстановки Лисков. Интерпретацией этого принципа может быть следующее: Для любого
> класса, который в будущем может понадобиться переопределить другим классом, должен быть определен
> “базовый” интерфейс. И это позволяет разработчикам использовать другую реализацию какого-то класса,
> определив методы этого интерфейса.

Специально посмотрел в оригинал и там тоже это есть. И тут же ссылка на википедию с правильным определением.
ИМХО, то что main contributor фреймворка не понимает LSP уже должно много говорить о фреймворке.
> $results = $this->events()->triggerUntil(__FUNCTION__, $this, $params, function ($r) {
> return ($r instanceof SomeResultClass);
> });

А вот здесь LSP и нарушается.
Не уловил где противоречие… Есть интерфейс EventCollection, описывающий поведение объектов, способных к агрегации обработчиков событий и «запуск» выполнения этих обработчиков.
EventManager — объект реализующий этот интерфейс, и соответственно он может агрегировать и исполнять обработчики.
И если Вас не устраивает реализация EventManager, вы можете написать свой объект, реализующий интерфейс EventCollection — со своей логикой — и использовать его заместо EventManager. Главное условие, чтобы:
«Поведение наследуемых классов не должно противоречить поведению, заданному базовым классом, т.е. поведение наследуемых классов должно быть ожидаемым для кода, использующего переменную базового типа.» (с) Wiki
Только базовый класс здесь — интерфейс EventCollection.
PS.: метод triggerUntil объявлен в интерфейсе EventCollection
Противоречие здесь не в EventCollection, а в том, что по сути не определен интерфейс самого обработчика события. Что приводит к этим самым проверкам на тип возвращаемого значения, для того чтобы понять какой все-таки хэндлер запустился.

Как правило, обработчики событий оперируют некими дескрипторами событий с определенным интерфейсом, в том числе в некоторых есть флаги остановки запуска следующих обработчиков.
По сути этот флаг здесь реализован через вызов функции stopPropagation(), но все равно, как-то оно все смущает.

Наверное я все таки что-то не понимаю…

1) Везде и во всем следовать ЛСП (для всего создавать базовый класс/интерфейс) довольно сложное занятие, и зачастую лишнее. Стараются эти интерфейсы определять для ключевых элементов фрэймворка.

2) Есть интерфейс EventDescription — если вы про него — оно и представляет собой событие (в статье про него не упоминается вроде)

3) есть класс ResponseCollection — он агрегирует результаты работы обработчиков.

4) Если вся претензия к «return ($r instanceof SomeResultClass)» то тут нет никаких противоречий.

$results = $this->events()->triggerUntil(__FUNCTION__, $this, $params, function ($r) {
return ($r instanceof SomeResultClass);
});

Это говорит Менеджеру событий исполнять все обработчики, подписанные на событие, до тех пор, пока один из них не вернет результат работы типа SomeResultClass. Что это за класс (а результат работы обработчика не обязательно должен быть объектом, скалярные тоже пойдут) фрэймворк не описывает.
— Если опять не в тему — то сорри — не понимаю :)
Мы не проверяем кто запустился, нам не важно это, нам важен результат который это кто-то выдал, эта проверка и осуществляется кодом анонимки. И это, имхо, не нарушает ЛСП. С другой стороны Вы правы, мы сами назначаем когда закончить отдавать обработчикам несчастное событие и вернуть результат, тоже считаю что не совсем верно, это должно осуществляться в обработчике, обработчик указал что всё, ребята, больше тут делать нечего — не отдаем событие другим. Но может пример не совсем удачный. Т.о. Метод triggerUntil не нужен, имхо, он просто добавляет дополнительный функционал (и гибкость?).
>мы сами назначаем когда закончить отдавать обработчикам несчастное событие и вернуть результат, тоже считаю что не совсем верно, это должно осуществляться в обработчике

Где-то это может и верно (JacaScript может,...), но идея событийности в zf2 (точнее в разрабатываемом прототипе MVC), заключается в том что обработчик ничего не знают о других обработчиках. И он не может знать нужно ли прерывать обработку других событий. Прерывание обработчиков — обязанность того — кто инициировал событие — инициатор события заинтересован в получении каких-то результатов от событий, а не наоборот (в этом смысле stopPropagation() — добавляет дополнительный функционал, а рабочей лошадкой является именно triggerUntil() ).

К примеру при выполнении ActionController::dispatch()
инициируется событие:

$result = $this->events()->trigger('dispatch', $e, function($test) {
return ($test instanceof Response);
});

т.е. метод dispatch исполняет всех обработчиков, пока какой-нибудь обработчик не вернет объект Response.
На событие 'dispatch' могут быть подписаны разные обработчики — кто то занимается кэшированием, ктото ведением логов, проверкой прав доступа, еще чтото. Но методу dispatch() это не важно, он всего лишь хочет, чтоб хоть ктонибуть вернул Response.
Ну и соответственно обработчикам тоже все равно кто инициировал событие, и чего он хочет — они просто делают свою работу.
както так :)
поправлюсь
stopPropagation() — не совсем дополнительная фишка событийности в zf2. А очень даже необходимый функционал.
Обработчикам нужен такой функционал, к примеру при проверки прав доступа: Если обработчик решит что недостаточно прав для доступа к какому-то ресурсу — то он вполне может захотеть прервать обработку события.
Я знаю, здесь не принято, но можете поподробнее рассказать?
Без проблем.

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

Теперь вы наследуете от класса A некий класс B, который каким-либо образом переопределяет поведение класса А. Так вот, LSP говорит нам о том, что мы должны реализовать класс B таким образом, чтобы он полностью сохранил внешний интерфейс класса А, и что некий внешний код, который использует эти классы ничего не должен знать о разнице между классами A и B.

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

Пример:
Вы пишете некий юнит-тест, который тестирует класс A, который в свою очередь зависит от некоего класса B. Для того чтобы тест был именно модульный, вы должны заменить класс B неким «фальшивым» классом MockB с таким же интерфейсом как и у класса B. Соответственно, класс A должен быть написан таким образом, чтобы он не видел разницы между B и MockB.
Немножко неточно. Суть не в сохранении интерфейса, так как он в любом случае сохраняется, а в сохранении и НЕ модификации поведения класса предка (А) в классе наследнике (В). Проще говоря, если вы в коде замените все экземпляры класса А на экземпляры класса В и после это все работает абсолютно корректно, то вы соблюли LSP.

Вы слегка перепутали LSP и обычный code by contract (использование интерфейсов вместо реализаций) в вашем примере с моками.
Отсюда — регулирование поведения кода в зависимости от типа пришедшего объекта, кроме случаев валидации аргументов, как правило уже bad smell.
Sign up to leave a comment.

Articles