C # События / Подписки… слушайте проекты без ссылок

Я разрабатываю приложение, пытающееся использовать шаблон Observer. В основном у меня есть базовая форма, из которой могут быть загружены различные компоненты (формы).

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

Если я хочу, чтобы один из компонентов прослушивал события, вызванные базовой формой (возможно, из меню и т. Д.), Я не могу достичь этого без необходимости добавлять ссылку на базовую форму в компоненте. Это вызывает "круговую ссылку".

Можно ли слушать / подписываться на события в проектах, на которые нет ссылок?

14.12.2008 22:19:23
6 ОТВЕТОВ

Вы можете использовать шаблон EventBroker, как показано в Библиотеке шаблонов и практик Microsoft .

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

0
14.12.2008 22:25:38

Вы можете решить это различными способами. Один простой способ - создать специальный класс, который знает о базовой форме и различных компонентах. Этот класс отвечает за создание формы и компонентов. Поскольку он знает о них, он может прикреплять обработчики событий к соответствующим событиям. По сути, он просто «подключает» методы обработчика событий к компонентам к событиям в базовой форме.

Другой способ - определить интерфейс с событиями, которые будут реализованы основной формой. Компонентам можно передать экземпляр этого интерфейса в своих конструкторах. Затем они могут присоединиться к обработчикам событий. Таким образом, компоненты знают только об интерфейсе, а не о базовой форме. Это применение принципа «зависит от абстракций, а не от реализаций». В этом случае базовая форма будет реализовывать интерфейс и обладать знаниями о компонентах, передавая себя им при их создании. Таким образом, зависимость является односторонней.

Окончательное решение, однако, заключается в использовании контейнера внедрения зависимостей, такого как StructureMap . У вас был бы метод конфигурации, который регистрирует базовый класс формы в качестве разработчика интерфейса по умолчанию и различные классы компонентов. Затем StructureMap может создавать экземпляры классов по мере необходимости, автоматически вставляя интерфейс в конструкторы.

1
14.12.2008 22:33:02

У Microsoft есть «Managed Extensibility Framework (MEF)», которая может вам помочь. Из обзора MEF:

MEF предоставляет стандартный способ для хост-приложения выставлять себя и использовать внешние расширения. Расширения по своей природе могут повторно использоваться в различных приложениях. Тем не менее, расширение может быть реализовано в зависимости от приложения. Сами расширения могут зависеть друг от друга, и MEF позаботится о том, чтобы они были соединены в правильном порядке (еще одна вещь, о которой вам не придется беспокоиться).

Обзор и материалы для загрузки MEF находятся по адресу http://www.codeplex.com/MEF/Wiki/View.aspx?title=Overview&referringTitle=Home.

1
14.12.2008 22:51:00

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

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

1
14.12.2008 23:33:36

Вы обеспокоены циклическими ссылками или циклическими зависимостями? Циркулярные ссылки (проблема времени выполнения) очень трудно избежать при использовании шаблона наблюдателя. Циркулярные зависимости (проблема времени проектирования) всегда могут быть избавлены.

Типичное использование шаблона наблюдателя в c # - для наблюдателя иметь ссылку на объект издателя и зарегистрировать себя в событии, используя что-то вроде:

publisherObject.SomeEvent += MyEventHandler();

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

Обычно это проблема, когда вы хотите, чтобы сборщик мусора убрал неиспользуемые объекты. «Скрытой» ссылки на обработчик событий наблюдателя внутри publisherObject достаточно, чтобы предотвратить сборщик мусора. Это иногда упоминается как проблема потерянного слушателя. Самый простой способ сделать это - поместить отписки о событиях в метод Dispose () наблюдателя (и не забывать вызывать его, когда вы избавляетесь от наблюдателя).

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

Если вас больше волнуют циклические зависимости, то самый простой ответ - строгая иерархия родитель-потомок, как предложил Quarrelsome. Родитель (наблюдатель) всегда знает о своих потомках, поэтому может вызывать свойства и методы для них напрямую, если это необходимо. Дети (издатели) не должны ничего знать о своих родителях. Они общаются вверх исключительно через события и возвращают значения функций. Затем связь между детьми направляется через общего родителя (события на пути вверх, вызовы методов на пути вниз).

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

Обратите внимание, что вы можете быть немного гибкими в отношении того, что вы считаете родителем и что такое ребенок. Тот факт, что ваша основная форма создает вложенную форму, не означает, что вы должны определять связи между родителями и детьми в этом направлении. Типичная архитектура стиля плагинов может иметь основную форму, создавая экземпляры плагинов. Но после создания сообщения, вероятно, будут воспринимать плагин как родитель / наблюдатель, а основную форму - как источник публикации / события.

0
16.12.2008 16:36:46

Просто создайте проект с классом для события:

public class BaseFormEventClass
{
    public EventHandler<EventArgs> BaseFormDidSomething;
}

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

public class MyComponent
{
    public MyComponent(BaseFormEventClass eventClass)
    {
        eventClass.BaseFormDidSomething += this.EventClass_BaseFormDidSometing;
    }
    // ...
}

public class BaseForm
{
    private BaseFormEventClass eventClass = new BaseFormEventClass();

    private void LoadComponents()
    {
        MyComponent component1 = new MyComponent(this.eventClass);
    }

    private void RaiseBaseFormDidSomething()
    {
        EventHandler<EventArgs> handler = eventClass.BaseFormDidSomething;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}
0
18.12.2008 10:01:55