Какие события происходят в системе Inversion of Control (Deverdency Inversion)?
Вверх или вниз?
Я очень визуальный человек. Я думаю о своем приложении как об иерархии, где верх - это корень, а нижний - лист.
Я также понимаю, что контейнеры IoC не знают об ответственности / функциях содержащихся в них объектов. Вместо этого содержащиеся объекты знают о своем контейнере, то есть "контексте", через некоторый абстрагированный интерфейс.
UP: (Способ не-IoC?) Должны ли мои события отправляться из нижней части моей иерархии и всплывать вверх через шаблон цепочки ответственности к их родителям, чтобы содержащиеся объекты не знали о своих контейнерах? Например, кнопка в моем графическом интерфейсе отправляет событие CLICKED, которое перехватывается окном прослушивающего контейнера, которое отвечает, закрывая себя.
ВНИЗ: (Способ IoC?) Должны ли мои события отправляться из верхней части моей иерархии контейнером и достигать содержащих слушателей, которые подписались непосредственно на контейнер, чтобы контейнеры не знали о своем содержимом? Например, окно контейнера отправляет событие CLOSED, которое принимается непосредственно содержащимися в кнопке объектами, которые отвечают, закрывая себя, а затем окно следует примеру, закрывая себя.
"Вверх" мне кажется естественным, но поскольку в IoC контейнер не знает о поведении содержащихся в нем объектов, я бы не хотел отвечать на их события.
Я понимаю, что МОЖНО прослушивать событие практически в любой части системы, но я хочу понять фундаментальные отношения между участниками IoC, чтобы правильно их структурировать. Я предполагаю, что люди обычно не просто разбрасывают события о своей программе, не принимая во внимание структурные отношения, зависимости и т. Д.
Мой вопрос возникает из-за распределения ответственности в системе IoC - это ответственность отдельного объекта за выполнение вызовов контейнера и ответственность контейнера за предоставление услуг своим зависимым объектам (что инвертирует парадигму не-IoC - отсюда и синоним "Зависимость"). Инверсия "). Кажется, это очень важный, фундаментальный компонент IoC - перераспределение обязанностей. Я считаю, что вызов функций объекта или прослушивание его событий ОБА являются примерами зависимости от другого объекта. Когда объект определяет свое собственное поведение на основе другого объекта, я называю это зависимостью, потому что один объект ЗНАЕТ другого, а также ЗНАЕТ, что делает другой. Он определил себя в контексте другого объекта. Как я понимаю, IoC настроен так, что содержащийся в нем объект зависит от контейнера, поэтому он должен знать все о контейнере. И НЕ ДОЛЖНО быть обязанностью контейнера знать о содержании объекта. Таким образом, для КОНТЕЙНЕРА прослушивание событий на внедренном объекте CONTAINED кажется мне неправильным распределением обязанностей, потому что это означает, что он знает кое-что о его содержимом.
Этот вопрос был перефразирован после изучения зависимости "Инъекция" и "Инверсия" различны. Предыдущий вопрос найден здесь.
2 ответа
Для краткости я буду называть объекты, содержащиеся в контейнере, как "клиентские объекты" или просто "клиенты"...
События могут законно течь в обоих направлениях, не вводя нежелательных зависимостей между контейнером и клиентами. Фактически, позволяя контейнеру получать события от клиентов, это отличный способ минимизировать эти зависимости. Они обеспечивают путь для слабой связи именно так, чтобы контейнер мог оставаться в неведении о том, что на самом деле клиенты, все еще общаясь с ними. Пока рассматриваемые события определяются контейнером, а не клиентами.
Вы, кажется, в основном озабочены идеей, что клиентские объекты будут когда-либо инициировать события, которые потребляются контейнером. Это было бы проблемой, если бы эти события были определены объектами клиента. Это, очевидно, создаст жесткую зависимость от клиентского объекта из контейнера; контейнер должен знать об этих клиентских событиях и кодироваться специально для них. Это победило бы основную идею IoC. Но если эти события определяются контейнером, то это не является проблемой - фактически это лучший способ сохранить контейнер свободно связанным с объектами клиента. Клиенты могут запускать эти события для потребления контейнером, но они не определяют эти события. Они могут запускать только те события, которые контейнер знает как прослушивать, как определено контейнером. (Конечно, они могут запускать другие события для других целей, но контейнер не будет знать или не заботиться).
Предположим, например, что контейнер предлагает возможность отображать и печатать "представления", которые являются небольшими блоками содержимого в пользовательском интерфейсе (это похоже на среду, на которую вы намекали в своем посте, так как вы упомянули так много примеров графического интерфейса пользователя). - но эти понятия применимы вне всякого отношения к GUI). Контейнер предоставляет событие Print, которое принимает некоторые параметры о том, что печатать (может быть, ссылка на интерфейс IPrintable, может быть, на печать необработанных данных, зависит от того, чего вы пытаетесь достичь с помощью IoC). Клиентские объекты, которые выполняются внутри контейнера, могут, когда пользователь нажимает одну из кнопок на клиентском объекте, вызвать событие Print. Затем контейнер получит это событие и обработает печать от имени объекта клиента. Или объект клиента может вызвать событие "CloseMe", которое получит контейнер, и в ответ уничтожит объект клиента, который вызвал событие (наряду с другой обработкой, включая, возможно, запрос пользователя, уверен ли он и т. Д.)
И наоборот, контейнер может инициировать события, когда происходят вещи, в которых заинтересованы объекты клиента. Опять же, все они определены только в контейнере. Но клиентские объекты могут подписываться на них. Из предыдущего примера контейнер может предоставить событие PrintFinished, которое клиентские объекты могли бы использовать, чтобы показать пользователю сообщение в их собственном пользовательском интерфейсе, говорящее "Ваш документ готов!" Контейнер может вызвать сообщение ApplicationClosing, которое все клиентские объекты могут использовать для разрушения любых собственных ресурсов, за которые они держатся, до полного закрытия контейнера. Это просто глупые, простые примеры, но, надеюсь, они демонстрируют идею.
Итак, в заключение, я думаю, что это не только законно, но и очень полезно, чтобы события передавались в обоих направлениях - от контейнера к клиентам и от клиентов к контейнеру. Главное, что не имеет ничего общего с "вверх" или "вниз", это кто определяет эти события. Контейнер определяет их все.
Контейнеры не должны знать свои компоненты или их поведение. Обязанности контейнера, как правило, являются областью зависимости экземпляра, а не связи. Контейнер должен быть обобщенной фабрикой.
Это причина, почему структурный граф!= Коммуникационный граф. Как я уверен, вы поняли, что как фабрика контейнер является корнем вашего графа объектов, но контейнер, отвечающий или публикующий событие, просто не имеет смысла.
Тогда вы хотите, чтобы кто-то занимался управлением событиями. Специализированный компонент, который знает о событиях и может предоставить один экземпляр заинтересованным сторонам. Вы хотите обобщенного брокера событий. Маршрутизатор событий, если хотите.
Вы могли бы вероятно найти много реализаций одного. Например, EventBroker от Cab, агрегатор событий Prism или мой любимый EventHub. Этот последний - кульминация опыта работы с Кэбом и переписки с Джереми Миллером и Гленном Блоком, Kent Boogaart.
Если вы используете контейнер, вы, вероятно, знакомы с внедрением зависимостей и инверсией управления. Кто-то, кому нужно опубликовать события, получает IEventHub и вызывает публикацию со строго типизированным предметным классом. Тот, кому нужно подписаться на событие, также получает IEventHub и реализует строго типизированный интерфейс ISubscriber [который в основном предоставляет строго типизированный метод Receive]. Вот и все.
Теперь события могут проходить в любом направлении [насколько позволяют бизнес-требования], как они должны.