Страницы

Поиск по вопросам

пятница, 20 декабря 2019 г.

Как организовать взаимодействие объекта с его наблюдателем

#c_sharp #net #шаблоны_проектирования


Допустим у меня есть класс A. В классе А есть набор эвентов, которые отражают какие
либо изменения внутри - изменения коллекций, изменение основных свойств/состояния.
И у объекта есть свойство - объект класса B, который должен следить за этими состояниями
и производить какую либо работу проанализировав набор всех изменений.

Логично чтобы при создании объекта класса B, передавать ему в конструктор ссылку
на объект класса A и привязываться к событиям.

Интересует есть какие либо другие способы, реализации или паттерны.
    


Ответы

Ответ 1



Интересует есть какие либо другие способы, реализации или паттерны В С# паттерн "наблюдатель" может быть реализован многими способами, например: На основе делегата На основе события При помощи строго типизированного интерфейса При помощи специальных интерфейсов IObserver/IObservable На основе делегата Решение на основе делегата предствляет собой классических колбек. Преимуществом данного метода является отсутствие явного, типизированного обработчика события (взаимоотношение между поставщиком и потребителем события никак не регламентируются) и простота метода – при возникновении события, метод класса Consumer вызывает аргумент-делегат: class Consumer { public void Event(Action callback) { ... callback(); } } При возникновении события будет вызван делегат act и весь его invocation list, таким образом поддерживается множественность обработки события. На основе события Метод похож на предыдущий, но с той разницей, что позволяет организовать подписку на события любому количеству обработчиков (без моделирования отношения 1:1 как в предыдущем методе) либо не иметь обработчиков вовсе. class Consumer { public event EventHandler Event; void RaiseEvent() { // Не самый потокобезопасный код if (Event != null) { Event.Invoke(this, new EventArgs()); } } } public class Program { public static void Main() { Consumer consumer = new Consumer(); consumer.Event += (object sender, EventArgs args) => { /* */ }; consumer.Event += (object sender, EventArgs args) => { /* */ }; } } Метод похож на предыдущий но не дает гарантий на присутствие хотя бы одного обработчика. При помощи строго типизированного интерфейса В этом методе потребитель обрабатывает события поставщика при помощи определенного интерфейса обработчика, строго формализуя отношения между поставщиком и потребителем. interface IEventHandler { void FirstEventHandler(); void SecondEventHandler(); } class Supplier : IEventHandler { public void FirstEventHandler() { /* Обработчик события */ } public void SecondEventHandler() { /* Обработчик другого события */ } } class Consumer { IEventHandler _supplier; public void Subscribe(IEventHandler supplier) { _supplier = supplier; } void RaiseFirstEvent() { _supplier.FirstEventHandler(); } void RaiseSecondEvent() { _supplier.SecondEventHandler(); } } public class Program { public static void Main() { Consumer consumer = new Consumer(); Supplier supplier = new Supplier(); consumer.Subscribe(supplier); } } Это почти классическая реализация паттерна подписчик в C# (и паттерна "делегат" в Objective-C с той лишь разницей, что все обработчики должны быть строго определены). Реализация этого метода требует пристального наблюдения на предмет нарушения SRP, так и согласованности событий поставщика. При помощи специальных интерфейсов IObserver/IObservable Начиная с .NET 4 доступны специальные интерфейсы IObserver/IObservable для реализации паттерна наблюдатель для последовательностей событий: class Data { } class Consumer : IObserver { public void OnCompleted() { } public void OnError(Exception e) { } public void OnNext(Data data) { // Обработка новых данных } } class Supplier : IObservable { List> _subscribers = new List>(); class Unsubscriber : IDisposable { private List>_observers; private IObserver _observer; public Unsubscriber(List> observers, IObserver observer) { _observers = observers; _observer = observer; } public void Dispose() { if (_observer != null && _observers.Contains(_observer)) _observers.Remove(_observer); } } public IDisposable Subscribe(IObserver observer) { _subscribers.Add(observer); return new Unsubscriber(_subscribers, observer); } void RaiseEvent() { foreach (var subscriber in _subscribers) { subscriber.OnNext(new Data()); } } } public class Program { public static void Main() { Consumer consumer = new Consumer(); Supplier supplier = new Supplier(); supplier.Subscribe(consumer); } }

Ответ 2



В "классическом" варианте паттерна "Наблюдатель" наблюдаемый субъект имеет методы: ЗарегистрироватьНаблюдателя(IНаблюдатель); УдалитьНаблюдателя(IНаблюдатель); ОповеститьНаблюдателей(); // этот приватный и имеются наблюдатели, которые реализуют некоторый интерфейс IНаблюдатель, который содержит метод Обновление(). Каждый наблюдатель содержит ссылку на наблюдаемый субъект и управляет подпиской/отпиской самостоятельно, либо получает ссылку в параметре метода Обновление(). По моему мнению, второй вариант предпочтительнее, так как первый "попахивает" наличием нескольких обязанностей, хотя могут быть и комбинации этих вариантов - например, наблюдатель содержит ссылку на наблюдаемый субъект, но подпиской не управляет и вообще ссылка эта публичная и извне ее можно заменить. Что касается C#: В C# присутствуют механизм событий, которые, по сути, и являются реализацией этого паттерна, поэтому наблюдаемому объекту специально ничего не нужно реализовывать, достаточно только выставить событие доступное для подписки и вызывать его при необходимости.

Комментариев нет:

Отправить комментарий