C #: события или интерфейс наблюдателя? За и против? - PullRequest
57 голосов
/ 15 февраля 2009

У меня есть следующее (упрощенно):

interface IFindFilesObserver
{
    void OnFoundFile(FileInfo fileInfo);
    void OnFoundDirectory(DirectoryInfo directoryInfo);
}

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

... и я в конфликте. Это в основном то, что я написал бы в C ++, но в C # есть события. Должен ли я изменить код для использования событий или оставить его в покое?

Каковы преимущества или недостатки событий по сравнению с традиционным интерфейсом наблюдателя?

Ответы [ 11 ]

64 голосов
/ 15 февраля 2009

Считать событие интерфейсом обратного вызова, в котором у интерфейса есть только один метод.

Только нужные события хуков
С событиями вам нужно только реализовать обработчики для событий, которые вы заинтересованы в обработке. В шаблоне интерфейса наблюдателя вы должны будете реализовать все методы во всем интерфейсе, включая реализацию тел методов для типов уведомлений, которые вам на самом деле не нужны. В вашем примере вы всегда должны реализовывать OnFoundDirectory и OnFoundFile, даже если вы заботитесь только об одном из этих событий.

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

Шаблон встроен в язык, поэтому все знают, как его использовать
События идиоматичны, потому что когда вы видите событие, вы знаете, как его использовать. С помощью интерфейса наблюдателя люди часто реализуют различные способы регистрации для получения уведомлений и подключения наблюдателя ... к событиям, хотя, как только вы научитесь регистрировать и использовать один (с помощью оператора + =), все остальные то же самое.

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

Синтаксис
Некоторым людям не нравится, как вы должны объявлять тип делегата для каждого события. Кроме того, стандартные обработчики событий в .Net Framework имеют следующие параметры: (отправитель объекта, аргументы EventArgs). Поскольку отправитель не указывает конкретный тип, вы должны понизить рейтинг, если хотите его использовать. Это часто хорошо на практике, если кажется, что это не совсем правильно, потому что вы теряете защиту статической системы типов. Но если вы реализуете свои собственные события и не следуете в этом отношении .Net Framework Convention, вы можете использовать правильный тип, чтобы потенциальное понижение не требовалось.

28 голосов
/ 15 февраля 2009

Хм, события могут быть использованы для реализации шаблона Observer. На самом деле, использование событий можно рассматривать как еще одну реализацию паттерна-наблюдателя imho.

8 голосов
/ 15 февраля 2009

Плюсы интерфейс-решения:

  • Если вы добавляете методы, существующие наблюдатели должны реализовать эти методы. Это означает, что у вас меньше шансов забыть подключить существующих наблюдателей к новым функциям. Конечно, вы можете реализовать их как пустые методы, что означает, что вы можете позволить себе роскошь ничего не делать в ответ на определенные «события». Но ты не так легко забудешь.
  • Если вы используете явную реализацию, вы также получите ошибки компиляции в другом случае, если вы удалите или измените существующие интерфейсы, то наблюдатели, реализующие их, прекратят компиляцию.

Минусы:

  • Больше нужно подумать о планировании, поскольку изменение интерфейса наблюдателя может привести к изменениям во всем вашем решении, что может потребовать другого планирования. Поскольку простое событие не является обязательным, мало или совсем не нужно менять другой код, если только этот другой код не реагирует на событие.
7 голосов
/ 27 марта 2012
  • События труднее распространять по цепочке объектов, например, если вы используете шаблон FACADE или делегируете работу другому классу.
  • Вы должны быть очень осторожны с отказом от подписки на события, чтобы разрешить сбор мусора для объекта.
  • События в 2 раза медленнее, чем простой вызов функции, в 3 раза медленнее, если вы делаете нулевую проверку при каждом повышении, и копируете делегат события перед нулевой проверкой и вызовом, чтобы сделать его потокобезопасным.

  • Также читать MSDN о новом (в 4.0) IObserver<T> интерфейсе.

Рассмотрим этот пример:

using System;

namespace Example
{
    //Observer
    public class SomeFacade
    {
        public void DoSomeWork(IObserver notificationObject)
        {
            Worker worker = new Worker(notificationObject);
            worker.DoWork();
        }
    }
    public class Worker
    {
        private readonly IObserver _notificationObject;
        public Worker(IObserver notificationObject)
        {
            _notificationObject = notificationObject;
        }
        public void DoWork()
        {
            //...
            _notificationObject.Progress(100);
            _notificationObject.Done();
        }
    }
    public interface IObserver
    {
        void Done();
        void Progress(int amount);
    }

    //Events
    public class SomeFacadeWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        private void RaiseDone()
        {
            if (Done != null) Done();
        }
        private void RaiseProgress(int amount)
        {
            if (Progress != null) Progress(amount);
        }

        public void DoSomeWork()
        {
            WorkerWithEvents worker = new WorkerWithEvents();
            worker.Done += RaiseDone;
            worker.Progress += RaiseProgress;
            worker.DoWork();
            //Also we neede to unsubscribe...
            worker.Done -= RaiseDone;
            worker.Progress -= RaiseProgress;
        }
    }
    public class WorkerWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        public void DoWork()
        {
            //...
            Progress(100);
            Done();
        }
    }
}
7 голосов
/ 15 февраля 2009

Некоторые дополнительные преимущества событий.

  • Вы получаете правильное многоадресное поведение бесплатно.
  • Если вы измените подписчиков события в ответ на это событие, поведение будет четко определено
  • Их можно легко и последовательно исследовать (отражать)
  • Поддержка цепочки инструментов для событий (просто потому, что они - идиома в .net)
  • Вы можете использовать асинхронный API-интерфейс, который он предоставляет

Вы можете достичь всего этого (кроме цепочки инструментов) самостоятельно, но это на удивление сложно. Например: Если вы используете переменную-член, такую ​​как List <>, для хранения списка наблюдателей. Если вы используете foreach для его итерации, то любая попытка добавить или удалить подписчика в одном из обратных вызовов метода OnFoo () вызовет исключение, если вы не напишите дополнительный код, чтобы разобраться с ним чисто.

3 голосов
/ 15 февраля 2009

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

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

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

2 голосов
/ 27 октября 2010

Преимущество интерфейсов в том, что к ним проще применять декораторы. Стандартный пример:

subject.RegisterObserver(new LoggingObserver(myRealObserver));

по сравнению с:

subject.AnEvent += (sender, args) => { LogTheEvent(); realEventHandler(sender, args); };

(я большой поклонник шаблона декоратора).

2 голосов
/ 15 февраля 2009

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

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

1 голос
/ 15 февраля 2009

Лучший способ решить это: какой из них лучше подходит для ситуации. Это может звучать как глупый или бесполезный ответ, но я не думаю, что вы должны рассматривать одно или другое как «правильное» решение.

Мы можем бросить в вас сотню советов. События являются лучшими, когда от наблюдателя ожидается прослушивание произвольных событий. Интерфейс лучше всего подходит, когда от наблюдателя ожидается перечисление всех заданных событий. События лучше всего подходят для приложений с графическим интерфейсом. Интерфейсы потребляют меньше памяти (один указатель для нескольких событий). Ядда Ядда Ядда. Обдуманный список плюсов и минусов - это то, о чем нужно подумать, но не окончательный ответ. Что вам действительно нужно сделать, это попробовать оба из них в реальных приложениях и почувствовать их. Тогда вы можете выбрать тот, который лучше подходит для ситуации. Учиться делать.

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

1 голос
/ 15 февраля 2009

Я предпочитаю решение для событийной базы по следующим причинам

  • Снижает стоимость входа. Гораздо проще сказать «+ = новый EventHandler», чем реализовать полноценный интерфейс.
  • Снижает затраты на обслуживание. Если вы добавите новое событие в ваш класс, это все, что нужно сделать. Если вы добавляете новое событие в интерфейс, вы должны обновить каждого потребителя в своей базе кода. Или определите совершенно новый интерфейс, который со временем будет раздражать потребителей: «Реализую ли я IRandomEvent2 или IRandomEvent5?»
  • События позволяют обработчикам не основываться на классах (т. Е. Где-то статический метод). Не существует функциональной причины, чтобы заставить все обработчики событий быть членами экземпляра
  • Группировка группы событий в интерфейсе делает предположение о том, как используются события (и это только предположение)
  • Интерфейсы не дают реального преимущества перед необработанным событием.
...