Как стать автором
Обновить

Комментарии 52

Я не очень понял, что вы предлагаете использовать вместо этого паттерна.

Вот и мне тоже интересно. Я согласен с автором, но какая альтернатива? Утки? (С)

Связи должны быть с наружи компонента, а не внутри. Сам компонент не должен решать куда ему встроиться. как это сделать? Напишу статью.
Использование EventAggregator нарушает 3 из 5 принципов SOLID.

Как-то многовато насчитали. Может проблема не в паттерне, а в не очень удачном его применении?


По SRP в коде модели светодиода не должно быть никаких подписок, только состояние "горит/не горит" и метод "переключить" (вариант "зажечь" и "погасить"). То же с кнопками — состояние и метод для его изменения. Отдельно собственно схема — модель, хранящая ссылки на модели кнопок и светодиодов, и реализующая основную логику методами "нажать кнопку повер", "нажать переключатель". И отдельно собственно подписчик на события интерфейса, преобразующий их в методы схемы.


Можно реализовать на событийной модели и агрегаторе и собственно бизнес-логику, но обязательно должен быть преобразователь событий интерфейса в события модели.


Вообще, если есть предположения, что приложение будет развиваться для моделирования произвольных схем (постоянного тока без учета переходных процессов для начала, для школы например), то я бы сделал "коммутатор", основная внешняя функция которого транслировать события "подано питание" с одного или нескольких входов на один или несколько выходов в зависимости от состояния "таблицы коммутации", плюс обрабатывать события изменения этой таблицы. Такими коммутаторами в схеме являются и кнопки, и светодиоды, и проводники их соединяющие, и даже отсутствующий источник питания. Проводники — вырожденный случай, транслирующие события с одного входа на другой без состояния, светодиоды (и любые подобные устройства) — то же самое, но имеющее побочный эффект, выключатель — почти тот же проводник, но имеющий состояние, указывающее генерировать ли событие на выходе при поступлении события на входе и метод для его переключения. Переключатель — почти тот же выключатель, но с состоянием, указывающим на какой из двух выходов транслировать событие со входа. Источник питания — коммутатор без входов и с одним выходом, который просто генерирует событие при старте моделирования. "Сборка" же схемы будет состоять из инстанцирования коммутаторов (трансляторов событий), инициализации их схем коммутации, привязки к некоторым из них исполнительных устройств, и, главное, подписки их на события друг друга через агрегатор согласно схеме, в идеале описанной декларативно, что позволит менять её бех кодирования, а читать из файлов, бд, или просто UI. Ответственность коммутаторов — транслировать события из агрегатора в агрегатор и, если с коммутатором связано (sic! композиция, а не наследование!) исполнительное устройство, дергать его соответствующий метод.

Схемы выбраны как абстракция. В схему можно заменить на систему фильтров — где Power и switch — критерии поиска, а Led'ы — результат фильтрации. Фантазировать можно как угодно.

В любом случае, если модель предметной области не событийная, то классы объектов модели надо стараться отделять от служебной событийной инфраструктуры. Впрочем это любой инфраструктуры касается.

Перед прочтением необходимо почитать о шаблоне EventAggregator

Ну так привели бы определение шаблона, или хотя бы ссылку дали. А то ведь не понятно, что именно вы под ним понимаете.


За отсутвием лучшего, возьмем фаулеровское определение: "An Event Aggregator acts as a single source of events for many objects. It registers for all the events of the many objects allowing clients to register with just the aggregator."


Управление светодиодами через EventAggregator

А кто вам сказал, что это подходящий шаблон для вашей задачи? Вернемся к Фаулеру: "Event Aggregator is a good choice when you have lots of objects that are potential event sources" (выделение мое). Это ваш кейс? Нет. У вас два семантически связанных, но при этом разнородных события.


А теперь представьте, что у вас задача вида: "у меня есть k источников новостей и m устройств, отображающих новости в виде скроллера". Вы предлагаете каждое из устройств подписывать на каждый источник? Получите k*m связей (и в каждом устройстве нужно будет делать обнаружение источников). А можно взять аггрегатор, который будет сам подписываться на все источники, а все устройства подписывать только на аггрегатор: получается k+m связей, логика обнаружения источников только в агрегаторе, в устройствах тривиальная подписка.


Собственно, если задуматься, половина модного нынче Rx — это как раз event aggregator.


Использование EventAggregator нарушает 3 из 5 принципов SOLID.

А нарушает ли? Давайте проверим.


Единственность ответственности – подписка/отписка не забота компонентов схемы.

А чья? В событийной модели кто-то должен отвечать за подписку/отписку. И этот кто-то, что характерно, не зависит от того, аггрегатор у вас, или просто обсервер. Так что пункт не засчитан.


Открытость закрытость – при изменении схемы взаимодействия компонентов, нужно править подписку/отписку.

Во-первых, нет. Подписка/отписка осталась той же самой — аггрегатор на все источники, все потребители на аггрегатор.


Во-вторых, тот факт, что при изменении схемы взаимодействия надо править код, не является подтверждением нарушения OCP. Если у вас изменилась бизнес-логика, логично, что меняется код, ее реализующий.


Пункт снова не засчитан.


Инверсия зависимости – компонент сам решает, на какие события подписываться/отписываться.

Иии что? До тех пор, пока компонент зависит от абстракции агрегатора, а не от конкретной реализации, DIP не нарушен.


Так что из трех названных вами пунктов в реальности не нарушен ни один.

А чья? В событийной модели кто-то должен отвечать за подписку/отписку. И этот кто-то, что характерно, не зависит от того, аггрегатор у вас, или просто обсервер. Так что пункт не засчитан.

Отличная система где все на все подписываются. В отличии от EventAggregator с его конкретным типом события, Observer взаимодействует с реализацией интерфейса. Реализация приходит в конструкторе или методе — наблюдатель не выбирает за чем наблюдать.
Во-первых, нет. Подписка/отписка осталась той же самой — аггрегатор на все источники, все потребители на аггрегатор.
Речь про добавление новых событий в системе. Вы использовали EventAggregator в своих проектах?

Иии что? До тех пор, пока компонент зависит от абстракции агрегатора, а не от конкретной реализации, DIP не нарушен.
))) Зависимость не от аггрегатора, а от конкретных событий.

lair мне кажется вы просто не сталкивались с EventAggregator. Это очень круто когда в системе 300 событий половина из которых дубляж, просто потому что событие с таким типом уже где то там используется и что бы ничего не сломать нужно создать то же самое событие но по другому назвать. А еще в системе присутствует не явная зависимость от последовательности подписки, и если ее нарушить все рухнет. Зафига там что то проектировать улучшать? Сделал новое событие, а потом еще и еще… EventAggregator способствует запутыванию кода.
Отличная система где все на все подписываются.

Чем она отличная? Тем, что количество связей растет с неимоверной скоростью?


В отличии от EventAggregator с его конкретным типом события, Observer взаимодействует с реализацией интерфейса.

А что мешает EA взаимодействовать с реализацией интерфейса?


Реализация приходит в конструкторе или методе — наблюдатель не выбирает за чем наблюдать.

...EA — это всего лишь промежуточный обсервер. Вам никто не мешает сделать так же.


Речь про добавление новых событий в системе.

Добавили новые события — поправили тех, кто от этих событий зависит. Все как с обсервером.


Вы использовали EventAggregator в своих проектах?

Да, просто не в описанной вами реализации.


Зависимость не от аггрегатора, а от конкретных событий.

Ну так это особенность событийной модели, а не EA.


Это очень круто когда в системе 300 событий половина из которых дубляж, просто потому что событие с таким типом уже где то там используется и что бы ничего не сломать нужно создать то же самое событие но по другому назвать.

А может это проблема того, что у вас в системе зачем-то должны быть уникальные типы событий?


А еще в системе присутствует не явная зависимость от последовательности подписки, и если ее нарушить все рухнет.

Это уж точно не проблема EA.


EventAggregator способствует запутыванию кода.

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

Чем она отличная? Тем, что количество связей растет с неимоверной скоростью?

именно об этом я и пишу, количество событий начинает разрастаться.

...EA — это всего лишь промежуточный обсервер. Вам никто не мешает сделать так же.

не мешает, но как обычно используют EventAggregator — проброс в конструктор и подписка на любые доступные события.

А что мешает EA взаимодействовать с реализацией интерфейса?

Практика использования — Подписывайся когда хочешь и на что хочешь.

Добавили новые события — поправили тех, кто от этих событий зависит. Все как с обсервером.

нет. Если объект сам решает на что ему подписываться.

Да, просто не в описанной вами реализации.

все персонажи вымышленные совпадения случайны.

А может это проблема того, что у вас в системе зачем-то должны быть уникальные типы событий?

Уникальные типы появляются, когда одни и те же данные имеют разное предназночение. Например строка может быть сообщением об ошибке, заголовком окна или еще чем то.

Это уж точно не проблема EA.

Даже у совершенного оружия есть недостаток — его владелец ©. Многие считают, что при использование EventAggregator не будет проблем, они ошибаются. Очень тяжело отслеживать события.

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

А разве EventAggregator не основан на событийной модели? Название даже содержит Event…
именно об этом я и пишу, количество событий начинает разрастаться.

События не могут увеличивать количество связей, при росте количества связей в худшем случае количество событий растёт так же.


Добавили новые события — поправили тех, кто от этих событий зависит. Все как с обсервером.
нет. Если объект сам решает на что ему подписываться.

Так решает и обсервер.


Многие считают, что при использование EventAggregator не будет проблем, они ошибаются. Очень тяжело отслеживать события.

Вы не путаете событийную модель вообще и один из её паттернов? Гораздо проще отслеживать события, если они все проходят через одной точку, чем когда у каждого события свой маршрут.


А разве EventAggregator не основан на событийной модели? Название даже содержит Event…

Вроде различаете, но как-то странно. EventAggregator предназначен для упрощения работы с событийной модели. Сначала принимается решение использовать событийную модель, а потом для её упрощения вводится EventAggregator.

Гораздо проще отслеживать события, если они все проходят через одной точку, чем когда у каждого события свой маршрут.

Проходят через одну точку? Регистрируются да, но уж точно не проходят.

В моей ментальной модели они именно проходят. Физическая реализация значения мало имеет.

Дело не в реализации. И как вы их будете отслеживать?

Подпишусь логгером на все события агрегатора и буду писать их в один лог. Или не на все, только на интересующие.

Подпишусь логгером на все события агрегатора

При добавлении нового события нужно не забыть подписать лог? А что это даст кроме информации что событие произошло?

Не нужно, если есть возможность подписаться на базовый класс Event, наследниками которого являются все события.

а какая польза будет от такого логирования?

Буду их отслеживать, потому что вам это нужно :)

Так решает и обсервер.

В случае наблюдателя имеется ограниченный набор на что можно подписаться, с агрегацией событий это не так.

Набор событий всегда ограниченный.

кто его ограничивает? Как скрыть не нужные события? В наблюдателе все это ограничивается интерфейсом, если у интерфейса десятки методов для подписки, то чем это отличается от аггрегатора?

Совокупность всех эмиттеров. Если каждый генерит по 10 типов событий и их всего 100, то число событий ограничено 1000.


Не надо подписываться на ненужные события. А то и не не надо их генерировать :)


Агрегатор собирает однотипные события в единый поток, позволяя реакторам подписываться на этот тип событий только в одном месте, а не переписывать код каждого наблюдателя при добавлении нового эмиттера.

Не надо подписываться на ненужные события. А то и не не надо их генерировать :)

С этим спорить сложно.

Агрегатор собирает однотипные события в единый поток, позволяя реакторам подписываться на этот тип событий только в одном месте, а не переписывать код каждого наблюдателя при добавлении нового эмиттера.

При добавлении нового эмитера нужно дорабатывать все реакторы которые ожидают новое событие.

В том-то и суть агрегатора, что не нужно дорабатывать все реакторы при появлении нового эмиттера, генерирующего уже существующее событие, в котором заинтересованы реакторы. Только агрегатору указать на новый источник (часто через конфиги, без правки исполняемого кода) и всё.

В случае наблюдателя имеется ограниченный набор на что можно подписаться, с агрегацией событий это не так.

Вообще-то, паттерн EA никак не подразумевает "неограниченный набор событий". В аггрегате ровно те события, которые вы решили аггрегировать, никаких больше.

агрегация ни как не поддается контролю.

Да ладно. Агрегатор А агрегирует события типа Х с критерием У, агрегатор Б агрегирует события типа Й с критерием У2, подписчик Ц подписывается на А, подписчик Д — тоже на А, а подписчике Е — на А и Б. Что тут "не поддается контролю"?

Сначала принимается решение использовать событийную модель, а потом для её упрощения вводится EventAggregator.

И как он ее упростит?

У каждого эмиттера событий в идеале только один подписчик — агрегатор, а каждый реактор на события подписывается только на один эмиттер — агрегатор. Количество связей эмиттер-реактор снижается в лучшем случае с N*M на N+M. Усложняет он схему распространения событий только в простейших случаях, когда только один эмиттер или только один реактор.

У каждого эмиттера событий в идеале только один подписчик — агрегатор, а каждый реактор на события подписывается только на один эмиттер — агрегатор.

Если подписка будет происходить вне эмиттера и реактора то да, иначе с заменой одной реализации конкретного эмиттера или реактора возникнут проблемы.

Не возникнут, если они используют единый интерфейс (в широком смысле слова).

О каком интерфейсе речь? Что хочу публикую, что хочу слушаю?

Вот у вас есть интерфейс сервиса. Если конкретная реализация этого сервмса внутри себя подписывается или публикует что либо, через аггрегатор. Что нужно сделать для замены реализации?

Публикую наследников от AbstractEvent, слушаю их же. Интерфейс публикации и подписки зафиксирован.


Заменить нужно старую логику подписок и публикаций на новую, не меняя интерфейса.

Если подписка будет происходить вне эмиттера и реактора то да

… реализация "подписки", заметим, не является зафиксированной частью паттерна EventAggregator. Так что ваши претензии снова не по адресу.

Не является. Но часто, в том же Prism, EventAggregator резолвится через IoC в конструктор.
Хуже всего что все примеры и документация на это заточены. Да это очень просто сделать, а вот сопровождать тяжело.
Да это очень просто сделать, а вот сопровождать тяжело.

Кому как.

именно об этом я и пишу, количество событий начинает разрастаться.

События должны определяться задачей. Почему они у вас разрастаются?


как обычно используют EventAggregator — проброс в конструктор и подписка на любые доступные события.
Практика использования — Подписывайся когда хочешь и на что хочешь.

Это проблема тех, кто его неправильно использует.


Если объект сам решает на что ему подписываться.

Это называется "инкапсуляция". Что в этом плохого?


Уникальные типы появляются, когда одни и те же данные имеют разное предназночение. Например строка может быть сообщением об ошибке, заголовком окна или еще чем то.

Во-первых, это не "одни и те же данные". А во-вторых, если мы не говорим про type-driven (а мы про него явно не говорим), то почему у вас тип данных и их предназначение связаны? Потому что у вас единственное, что определяет событие — это его тип?


Многие считают, что при использование EventAggregator не будет проблем, они ошибаются.

… но это не проблема EA. Это проблема тех, кто так считает. Недостатки EA — те же, что и у обсервера — явно упомянуты в описании паттерна.


А разве EventAggregator не основан на событийной модели? Название даже содержит Event…

Конечно, основан. У вас есть претензии к событийной модели? Понимаю, окей. Откажитесь от событийной модели вовсе.


… и чем это отличается от вашего ""

События должны определяться задачей. Почему они у вас разрастаются?

Как и всегда добавляются новые требования. Самое простое решение которое предлагает аггрегатор — добавить новое событие и подписаться везде где это нужно. Вот только не всегда это адекватное решение.

Это проблема тех, кто его неправильно использует.

не используя нет таких проблем. Всегда конечно есть другие.

Это называется «инкапсуляция». Что в этом плохого?

При такой «Инкапсуляции» маршрут данных в приложении тяжело менять.

Потому что у вас единственное, что определяет событие — это его тип?

В системе уже используется событие с определенным типом, теперь я хочу пере использовать тип события, но не могу. Уже есть источники и подписчики, а мне нужно что бы источники и подписчики нового события ни как не были связаны с источниками и подписчиками уже используемого. Это тяжко сделать с EventAggregator который предоставляет Prism, Catel и тд.

У вас есть претензии к событийной модели? Понимаю, окей. Откажитесь от событийной модели вовсе.blockquote>
Уже.
Самое простое решение которое предлагает аггрегатор — добавить новое событие и подписаться везде где это нужно.

Аггрегатор ли? В описании паттерна такого нет.


При такой «Инкапсуляции» маршрут данных в приложении тяжело менять.

Или, наоборот, легко, потому что данные потребляются ровно там, где они нужны.


В системе уже используется событие с определенным типом, теперь я хочу пере использовать тип события, но не могу.

Почему не можете?


Уже есть источники и подписчики, а мне нужно что бы источники и подписчики нового события ни как не были связаны с источниками и подписчиками уже используемого.

Говорю же: ваша проблема из-за того, что у вас один глобальный аггрегатор, в котором события различаются только типами. Это не проблема паттерна.

В системе уже используется событие с определенным типом, теперь я хочу пере использовать тип события, но не могу. Уже есть источники и подписчики, а мне нужно что бы источники и подписчики нового события ни как не были связаны с источниками и подписчиками уже используемого.

Странно как-то. В рамках одного контекста обычно или все события одного типа нужно обрабатывать одинаково, или это события разных контекстов, пускай и несущие одну семантику и структуру, тогда у каждого контекста должен быть свой агрегатор и всё. Или вы чисто по структуре типы назначаете, типа есть текстовое поле в типе события, зачем мне создавать новое?

А теперь представьте, что у вас задача вида: «у меня есть k источников новостей и m устройств, отображающих новости в виде скроллера». Вы предлагаете каждое из устройств подписывать на каждый источник? Получите k*m связей (и в каждом устройстве нужно будет делать обнаружение источников). А можно взять аггрегатор, который будет сам подписываться на все источники, а все устройства подписывать только на аггрегатор: получается k+m связей, логика обнаружения источников только в агрегаторе, в устройствах тривиальная подписка.


Ну это вы описали хороший кейс использования EA. У меня было больше плохих кейсов — вида «надоело разбираться с MEF'ом (или любым другим DI-контейнером) и взаимодействовать через интерфейсы, сделаем ка мы штуку, которая броадкастит события, которые мы потом будем в ручную диспетчеризовать в получателях по строковым тегам». По первости это все дело работает, но по мере его внедрения и увеличения количества передаваемых типов сообщений начинает разваливаться. Компилятор всю эту систему не проверяет и она начинает жить своей жизнью. Возможно именно эти случаи имел ввиду автор.
Да. Обратная сторона одного события. Всю информацию засунуть в одно событие, пусть подписчик разбирается сам.

Система спама, контролировать очень сложно.
У меня было больше плохих кейсов — вида «надоело разбираться с MEF'ом (или любым другим DI-контейнером) и взаимодействовать через интерфейсы, сделаем ка мы штуку, которая броадкастит события, которые мы потом будем в ручную диспетчеризовать в получателях по строковым тегам».

Так это же не EA. Броадкаст и диспетчеризация — вообще не EA уже.


(ну и вообще, любой паттерн хорош тогда, когда он применен по месту)

ну и вообще, любой паттерн хорош тогда, когда он применен по месту

да. Вот только EventAggregator так просто применить не по месту.

Тем не менее, это не проблема паттерна. "Антипаттерн" — это когда любое применение, даже "по месту", несет больше проблем, чем пользы.

Сегодня паттерн применяешь по месту, а завтра его превращают в анти. Вот чья это проблема?

Как можно "превратить паттерн в анти"?


Но вообще, если кто-то приходит и меняет использование компонента, не проследив за существующими пользователями — он, как бы, не прав. Не надо контракт нарушать.

Это как не надо жать на красную кнопку. Ведь найдется тот кто нажмет. Ломать систему должно быть по сложнее, чем просто добавить несколько событий.

Если вы хотите усложнить поломку системы — добавляйте статические проверки, добавляйте тестирование, и все это перед коммитом в мейнлайн. Никак иначе. И с событиями это не связано, можно и на ноль делить.

НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации