Pull to refresh

Comments 64

А зачем вам настолько слабая связность? Вам так хочется разбираться, как ваши компоненты взаимосвязаны через эти сигналы?

Честное слово, была у меня система на loosely coupled events — мы устали ее поддерживать.
Мне нравится что не приходится заботиться об асинхронности, все делается разметкой, нравится то что не приходится писать события и делегаты, проверять есть ли на них подписчики.
Это только попытка перехода, пока я запустил только один рабочий проект. Возможно окажется что действительно не стоит, но если код документорован и сигналы описаны, разбираться в нем проще чем в коде скрытом за паттернами.
Мне нравится что не приходится заботиться об асинхронности

Если вы о ней не заботитесь — вы ее не контролируете. Иногда это критично.

все делается разметкой

Это не достоинство само по себе.

нравится то что не приходится писать события и делегаты, проверять есть ли на них подписчики.

Есть больше одного способа это не делать.

разбираться в нем проще чем в коде скрытом за паттернами

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

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

А это как раз часто важно (например, транзакции).

Способ понижать связность много, описан один из них, пока в качестве эксперимента.

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

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

Правда? Назовите мне простой способ понять, что произойдет, когда (в вашем примере) будет обнаружен файл в папке.

(и сравните трудоемкость с решением той же задачи при «стандартном» решении)

я еще не пробовал такой подход в большом проекте

Как я уже говорил, мы пробовали LCE в большом проекте. Реально он применим только для задач класса «событие, реакция на которое нам не важна»).
Если в дебаге, я просто сталю бряк на обработчике сигнала. Если я беру код проекта и хочу понять что будет при таком-то сигнале, поиском нахожу все обработчики сигнала и вижу.
Если я беру код проекта и хочу понять что будет при таком-то сигнале, поиском нахожу все обработчики сигнала и вижу.

Поиском? Очень смешно. Особенно при желании понять очередность выполнения.

Сравните это с линейным чтением кода (и, по необходимости, нажатием F12) — и вы поймете, почему я считаю, что события/сигналы радикально влияют на читаемость.
Так суть подхода том и заключается очередность может быть в обработке сигналов одним обработчиком, но ее не должно быть при обработке одного сигнала всеми подписчиками, этим и вызвано требование неизменности данных после передачи их в сигнале.
ее [очередности] не должно быть при обработке одного сигнала всеми подписчиками

Кто вам это сказал?

Ну и да, проблема-то даже не в этом, а в том, сколько усилий нужно на отслеживание пути выполнения (особенно с учетом потенциальных рантайм-модификаций).
Мне это не сказали, это я так определяю требование к своей системе. Я хочу чтобы сигнал о событии обрабатывался одновременно всеми кто хочет о нем знать, и в тоже время чтобы последовательность сигналов обрабатывалась последовательно при необходимости теми обработчиками которые в этом нуждаются. Сейчас так и работает.
Вы продолжаете игнорировать проблему читабельности. Ну и ладно.
Нет же, я просто еще в ней не убедился, все постепенно, сейчас прочитаю всю критику, просмотрю ссылки на статьи, допилю и попробую оценить заново в более крупном проекте. Я вполне верю в ваш опыт, но хочу заиметь и свой)
Посмотрите в Orchard такую штуку как EventBus. Активно пользуюсь в своих проектах.
Посмотрел, я стараюсь добиться максимальной производительности, а в указанном проекте, есть такие вещи:
 Type type = eventHandler.GetType();
            foreach (var interfaceType in type.GetInterfaces()) {
                if (String.Equals(interfaceType.Name, interfaceName, StringComparison.OrdinalIgnoreCase)) {
                    return TryInvokeMethod(eventHandler, interfaceType, methodName, arguments, out returnValue);
                }
            }

Т.е на каждый вызов обработчика мы используем Refleсtion, что плохо скажется на скорости.
Ну и к тому же, насколько я понял, этот код не берет на себя распараллеливание работы обработчиков, и в нем можно отслеживать окончание обработки сообщения?
Посмотрите вот от этой переменной _interfaceMethodsCache.
Этот код нет, но никто не мешает его слегка доработать. Но у него есть огромный плюс в плане строгой типизации сообщений. Так же можно централизованно обрабатывать ошибки. И немножко магии для того что бы не референсить интерфейс обработчика. Достаточно в своем нэймспэйсе назвать интерфейс таким же именем и сделать такие же сигнатуры. Это может быть очень критично, если у вас развитая система модулей и плагинов.
Зачем делать ID сигналов? Почему не сделать их типизированными, заодно получив возможность сопоставлять сигналу какие-то данные?
Если вы имеете в виду что стоило создать базовый класс сигнала, и от него определять собственный сигналы, то ушел от этого сознательно, чтобы не загромождать код. Все сводилось к максимальным упрощениям.
Ваш подход ведёт к тому, что сигнал не может иметь именованных атрибутов, что очень не удобно. А обойтись можно и без базового класса.
Так же из хотелок:
1) возможность вручную подписываться на сигнал, передавая делегат
2) возможность навешивания со стороны подписчика дополнительных фильтров, указывающих, когда его надо вызывать
Первое скорее всего добавлю, по сути все есть для такой реализации кроме необходимости, декларативная разметка все таки удобна.
Второе — специально не стал добавлять, это я делаю в другом проекте по обмену сообщениями между компонентами, здесь посчитал лишним, т.к. снизит производительность.

Зачем сигналу именованные аттрибуты? Ведь по сути это однозначный идентификатор какого-то события, которое не должно толковаться по разному.
Событие: со сканера отпечатков получена картинка.
Атрибуты, без которых событие совершенно бессмысленно:
1) полученная картинка,
2) номер сканера или ссылка на объект, его представляющий,
3) метка времени получения события.

Из уже три. Куда их положить? В массив — и во что превратится код после этого? В анонимный класс — не получится. Остается только создать свой составной тип данных, который по хорошему и надо называть сигналом.
В статье это описано, данные описывающие все эти три поля лежат в объекте класса (ScanerStamp например), ссылка на который передается в сигнале, и не нужно никаких массивов, быстро и просто.
Я именно про это и написал. Появляется новый класс — ScanerStamp. Вопрос: почему надо обязательно оборачивать его в сигнал, и нельзя сделать его самого сигналом?
Можно, но не стоит. Пример:
путь будет чат, у нас есть класс Message, с данными о сообщении, и есть сообщения от пользователя и от системы. Если я сделаю класс Message сигналом, я не смогу подписаться отдельно на сообщения пользователя, и на сообщения системы, придется делать базовый класс Message, наследовать UserMessage, SystemMessage. А если использовать идентификаторы сигналов, то я просто подпишусь на MessageSignal.User и на MessageSignal.System, не добавляя никакие дополнительные сущности.
> Если я сделаю класс Message сигналом, я не смогу подписаться отдельно на сообщения пользователя, и на сообщения системы, придется делать базовый класс Message, наследовать UserMessage, SystemMessage.

А что мешает иметь один класс на два сигнала?
Тогда может я неправильно понял что имелось в виду, можно всевдокодом показать как стоит решить в вашем понимании описаную схему, когда я хочу в разных обработчиках получать разные сигналы но с данными описанными одним классом (Message)?
Схема совпадает с вашей, но вместо единого класса rtSignal можно указать любой свой.
Сейчас я посылаю сигнал так:
Signal(ID, state)

Ловлю так:
[SignalHanlder(ID)]
Обработчик(rtSignal signal)
{
/*достаю данные*/
SomeType data = (SomeType)signal.State;
}

По сути это аналог асинхронных операций в C#, где в асинхронную функцию передается object state. Я не могу все равно понять как вы предлагаете решить, при генерации сигнала я и сейчас могу указать в качестве передаваемых данных любой класс, и получить к нему доступ через signal.State
Вот только в C# асинхронные операции уже давно умеют работать с типизированными данными.
Добавлю и здесь, это не суть, сейчас мне важно понять вообще стоит ли двигаться дальше в этом направлении. Если стоит, то допилить не проблема.
Проблема этого подхода в том, что нет ровным счетом никакой связи между тем, что вы положите в сигнал, и достанете из сигнала. Иными словами, у вас обмен данными между компонентами становится нетипизованным. Привет ошибкам времени выполнения.
Это да, бесспорно, буду думать как оптимальней решить передачу со строгой типизацией.
Да, и еще есть такая вещь, которая с развитием процессоров становится все более актуальной, это асинхронность, microsoft делает много хорошего в этом направлении, тот же PLINQ, всякий сахар вроде await, но все это делается все равно в привычных рамках ООП, и нам все еще приходится самим создавать потоки, пускай и в виде тасков, но самим. Нужно отслеживать окончание исполнения задач, чтобы определить когда рессурсы станут ненужными.


Не желаете метнуться в сторону F# с его MailboxProcessor, AsyncWorkflows, и тысячей и одной других приятных штук?
Не могу ответить, нужно изучать, F# интересен давно, нет пока времени.
В таком подходе было бы круто уйти от атрибутов и основываться на именовании методов и классов.
То есть в место
[rtSignalAsyncHanlder(BufferSignal.FileInBuffer)]
void ProcessFileInBuffer(rtSignal signal)

Писать что то типа
void AsyncSignalProcessFileInBuffer(rtSignal signal)
Тогда мы уйдем от intellisense. Именно ради него атрибуты и заведены.
Простите, intellisense где? Для написания этих же атрибутов? Или я просто не понял про что вы…
Да, именно, т.е. когда я указываю что вот этот метод будет обрабатывать вот этот сигнал, я не ошибусь в написании какой именно сигнал будет обработан.
Я если чесно не совсем понял чего вы хотелись добиться, поэтому могу ошибаться.

Судя по всему вы изобретаете Actor Model, в .NET уже есть куча реализаций: уже упомянутый MailboxProcessor, TPL Dataflow, ActorFX и так далее. Плюс похожие схемы легко реализуются на Reactive Extensions.

Если суть в диспатчерах (команда-хендлер) то выделенных проектов насколько я знаю нема, но есть милионы cqrs фреймворков, которые это делают. Технически это четыре строки кода (особенно если пользоваться dynamic кейвордом).
Я хочу попробовать изменить архитектуру приложений на .NET, перейти на более слабосвязную. Для этого сделал этот проект, в принципе он аналогичен указанным в статье сигналам и слотам. Сейчас я хочу определить насколько удачен этот подход, для чего перевожу на него часть рабочих проектов, и справшиваю здесь мнения и советы.
Подход с разделением на команды(ваши сигналы) и хендлеры ипользуется уже кучу времени в .NET. Можете взяглянуть на NCQRS, можно на ICommand в WPF, NServiceBus. Даж в моем микро проектике — комманд диспатчер — github.com/chaliy/zaz. Просто задача настолько простая что никто это в отдельные библиотеки не выносит.

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

Дело в том, что использование паттернов и программирование от интерфейсов — это best practice, применяемый всеми нормальными разработчиками на протяжении долгого времени. Ваша схема, к сожалению, создаст эффект снижения читабельности, потому что велосипед. Я, честно говоря, не понял, зачем вы используете термин «сигналы» вместо «события», поясните разницу? Реализаций event-driven подхода в .net хватает.

Возможно, это неясно изложено в статье, но мне кажется, вы немного путаете проблемы. Plinq, await и асинхронность используется для параллельной обработки, а вовсе не для снижения связности кода. Если мы говорим о межпотоковом взаимодействии, то сигналы имеют смысл, но лучше Вам положиться на готовые реализации, чем использовать свою — это сложная тема и вы неминуемо наступите на грабли.
Гляньте ZMQ, не знаю, как там с .net интеграцией, но, может, концептуально будет интересно.

Удачи!

1. События означают что один класс знает о другом и об его события, т.е. я говорю classInstance.EventName += ()=>{}, использование сигналов позволяет мне уйти от этого, класс теперь должен знать только о событии и ему неважно какая именно реализация какого класса его сгенерирует.
2. Я не путаю, асинхронность просто является следствием подхода, мне не нужно теперь писать в коде создание потоков, достаточно указать атрибут rtSignalAsyncHanlder, и обработка сигнала будет вестись асинхронно.
ZMQ — это очереди сообщений для распределенной архитектуры. У меня же очереди сигналов в рамках одного приложения (где возможна передача данных по ссылке).
ZMQ может использоваться в рамках одного приложения, передача данных по ссылке это в 98% случаев минус. Даже в Вашем примере передача по ссылке не нужна.
В статье речь о С#. Меняем сигналы на евенты, а слоты на делегаты\методы — получаем Event Aggregator.
Зачем мне менять на Event'ы, если вся суть подхода чтобы от них уйти?
Я не о тех евентах о которых Вы. О типизированных. У нас же С#.
Например:
public class UserSelected
{
	private readonly int userId;

	public UserSelected(int userId)
	{
		this.userId = userId;
	}

	public int UserId
	{
		get
		{
			return this.userId;
		}
	}
}


Вы видимо не прочитали что из себя представляет паттерн Event Aggregator.
В языке программирования C# есть похожая конструкция с другой терминологией и синтаксисом: события играют роль сигналов, а делегаты — роль слотов.

Это, собственно, цитата из ссылки, которую вы дали.
Похожая — это не значит что есть аналог, аналог я и пытаюсь сделать чтобы не пользоваться похожей конструкцией.
Ну если Гит/Меркуриал не знаете, то с Subversion — самое оно на GitHub.
А в чем разница, чем SourceForge хуже? Я искал по .NET Open Source, чтобы и код выложить и можно было оставлять по багам замечания. Ну и на SourceForge также svn есть.
Злоупотребление эвентами ухудшает читабельность кода, так как трудно отследить всех подписчиков. Но можно найти хотя бы источник событий, так как они являются частью интерфейса. В вашем же случае с читабельностью еще хуже, так как отследить невозможно не только подписчиков, но и источник — сигналы не входят в интерфейс. Ctrl+F в данном случае костыль, так как ищет по строкам и не понимает семантики. Не совсем понял, как управлять областью видимости сигналов, и возможно ли это в принципе.
Пляжный волейбол, компонент команда ожидает сигнала от компонента Капитан команды, который этот сигнал и подает на картинке.
Sign up to leave a comment.

Articles