2009-02-15 3 views
53

я получил следующее (упрощенно):C#: события или интерфейс наблюдателя? За и против?

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

class FindFiles 
{ 
    IFindFilesObserver _observer; 

    // ... 
} 

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

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

ответ

63

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

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

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

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

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

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

+2

Небольшой интерфейс для интерфейсов заключается в том, что, когда у вас много событий, которые обычно объединяются, вам не нужно регистрировать их все в огромном блоке событий + = s, но регистрировать только один интерфейс. Я все же предпочитаю события, хотя, конечно :) – configurator

+0

Один из событий для событий - это то, что они не срабатывают через домены приложений. Если приложение является (или будет) сложным, это станет проблемой. – TMN

+10

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

28

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

+11

Абсолютно. Это немного похоже на вопрос: «Должен ли я использовать шаблон итератора или использовать foreach и IEnumerable?» –

+0

События, выпущенные у вашего наблюдателя, являются естественным способом уведомить несколько потребителей об изменении приложения – JoshBerke

+6

Я знаю, что это шаблон наблюдателя; Я спросил, должен ли я реализовывать шаблон, используя интерфейс обратного вызова или используя события. –

3

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

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

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

+0

Ну нет ничего, что мешает вам иметь одно событие C#, которое отличает разные события от дочернего класса EventArgs - я бы этого не сделал. –

8

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

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

Минусы:

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

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

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

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

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

1

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

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

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

+0

Вот почему я попросил «за»/«против». Я понимаю, что нет решения «одного размера подходит всем». Это было то, что я взвешивал и думал, что это будет интересный вопрос. –

7

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

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

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

2

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

subject.RegisterObserver(new LoggingObserver(myRealObserver)); 

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

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

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

+0

Так же легко применить узор декоратора по интерфейсу. Просто используйте инструмент производительности с вашей IDE, если все типизация становится проблемой. – Christo

0

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

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

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

  • Также read MSDN about new (in 4.0) IObserver<T> interface.

Рассмотрим следующий пример:

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(); 
     } 
    } 
}