Шаблон наблюдателя с делегатом и событиями с использованием массива событий

#c# #.net #observer-pattern

#c# #.net #шаблон наблюдателя

Вопрос:

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

 public delegate void NewDataAddedDelegate();
public event NewDataAddedDelegate NewDataAdded;
  

и для наблюдателя:

 qManager.NewDataAdded  = new qManager.NewDataAddedDelegate(getNewDataFunc);
  

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

 public delegate void NewDataAddedDelegate();
public event NewDataAddedDelegate [] NewDataAdded;  // can't do this
  

и в конструкторе qManager:

 NewDataAdded = new NewDataAddedDelegate[numberOfQueues];
  

и в наблюдателе:

 qManager.NewDataAdded[0]  = new qManager.NewDataAddedDelegate(getNewDataFunc0);
qManager.NewDataAdded[1]  = new qManager.NewDataAddedDelegate(getNewDataFunc1);
  

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

Есть идеи о том, как подойти к этой проблеме?

Ответ №1:

Нет, события так не работают. Опции:

  • Создайте другой тип, который предоставляет событие, и создайте массив или коллекцию этого типа:

     // Preferably *don't* just expose an array...
    public TypeWithEvent[] Queues { get { ... } }
    
    // Subscription:
    qManager.Queues[i].NewDataAdded  = ...
      
  • В качестве альтернативы, не используйте события, а просто используйте метод:

     private NewDataAddededDelegate[] newDataAdded;
    
    public void SubscribeNewDataAddedHandler(int queue, 
                                             NewDataAddedDelegate handler)
    {
        newDataAdded[queue]  = handler;
    }
    
    // Subscription
    qManager.SubscribeNewDataAddedHandler(0, ...);
      

Лично мне кажется, что каждая очередь действительно должна быть отдельным объектом, хотя … make the queue manager предоставляет коллекцию очередей, на каждую из которых можно подписаться по отдельности. (т. е. используйте первый подход.) В противном случае ваш диспетчер очередей действительно выполняет слишком много работы.

Ответ №2:

Есть два подхода, которые вы могли бы использовать здесь; первый заключается в том, чтобы иметь:

 private NewDataAddedDelegate[] queues; // init not shown
public void Subscribe(int index, NewDataAddedDelegate handler) {
    queues[index]  = handler;
}
  

и использовать

 obj.Subscribe(index, ...);
  

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

 public class SomeQueue {
    public event NewDataAddedDelegate NewDataAdded;
}
  

а затем предоставьте их, возможно, через индексатор, так что у вас есть

 obj.Queues[index].NewDataAdded  = ...
  

Лично я ожидаю, что первое проще. Это только синхронизация, которая может быть неприятностью. Я делаю это в некотором коде pub-sub, и IIRC я просто lock во время подписки.

Ответ №3:

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

Определите свои интерфейсы IObserver и ISubject и попытайтесь понять, что такое observer и каковы субъекты. В вашем случае звучит так, как будто субъектами являются очереди, не уверен, какими будут наблюдатели в вашей модели домена.

Как только вы это сделаете, все будет легче понять, и это просто вопрос реализации методов, объявленных вашими интерфейсами, например, ваши субъекты (очереди) просто вызовут notify (и вызовут событие, если вы хотите использовать делегатов), когда что-то произойдет (элемент, добавленный в очередь).

Надеюсь, это поможет.

Ответ №4:

Вот рабочий код на C #.

QueueManger предоставляет событие NewDataAddedEvent, которое может быть подписано одним или несколькими наблюдателями. Очередь вызывает метод NewDataAdded() в QueueManager при изменении данных. QueueManager уведомляет, есть ли какие-либо подписчики с параметром Queue. Я надеюсь, что это отвечает на ваш вопрос.

 using System;
using System.Collections.Generic;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            QueueManager queueManager = new QueueManager();
            Observer observer = new Observer(queueManager);
            Queue queue1 = queueManager.AddQueue();
            Queue queue2 = queueManager.AddQueue();

            queue1.OnNewDataAdd();
            queue2.OnNewDataAdd();

            Console.ReadLine();
        }


        delegate void NewDataAddedDelegate(Queue queue);


        class Queue
        {
            QueueManager queueManager;
            public string id;
            public Queue(string id, QueueManager queueManager)
            {
                this.id = id;
                this.queueManager = queueManager;
            }

            public void OnNewDataAdd()
            {
                this.queueManager.NewDataAdded(this);
            }
        }

        class QueueManager
        {
            List<Queue> queues = new List<Queue>();

            public Queue AddQueue()
            {
                Queue queue = new Queue((queues.Count   1).ToString(), this);
                this.queues.Add(queue);
                return queue;
            }

            public event NewDataAddedDelegate NewDataAddedEvent;
            public void NewDataAdded(Queue queue)
            {
                if (NewDataAddedEvent != null)
                    NewDataAddedEvent(queue);
            }
        }

        class Observer
        {
            public Observer(QueueManager queueManager)
            {
                queueManager.NewDataAddedEvent  = new NewDataAddedDelegate(queue_NewDataAdded);
            }

            void queue_NewDataAdded(Queue queue)
            {
                Console.WriteLine("Notification to the observer from queue {0}", queue.id);
            }
        }

    }

}
  

Ответ №5:

Возможно, вы могли бы использовать шаблон агрегатора событий.

Не то чтобы вам пришлось бы кодировать меньше, но это могло бы создать более чистый / поддерживаемый код.