C#: события или интерфейс наблюдателя? Плюсы/минусы?



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



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

class FindFiles
{
IFindFilesObserver _observer;

// ...
}


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



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

750   11  

11 ответов:

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

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

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

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

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

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

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

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

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

плюсы:

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

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

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

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

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

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

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

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

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

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

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

subject.RegisterObserver(new LoggingObserver(myRealObserver));

против:

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.