Подтвердить что ты не робот

С#: события или интерфейс наблюдателя? За и против?

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

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

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

Минусы:

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

Ответ 4

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

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

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

Ответ 5

  • События сложнее распространять через цепочку объектов, например, если вы используете шаблон 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();
        }
    }
}

Ответ 6

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

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

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

Ответ 7

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

У С# есть поддержка анонимных делегатов - lambdas - и поэтому события могут использоваться в С#.

Ответ 8

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

subject.RegisterObserver(new LoggingObserver(myRealObserver));

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

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

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

Ответ 9

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

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

Ответ 10

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

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

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

Ответ 11

Если ваши объекты должны быть сериализованы каким-то образом, которые сохраняют ссылки, такие как NetDataContractSerializer или, возможно, protobuf события не смогут пересечь границу сериализации. Поскольку шаблон наблюдателя основан только на объектных ссылках, он может работать с этим типом сериализации без проблем, если это то, что вам нужно.

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