#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:
Возможно, вы могли бы использовать шаблон агрегатора событий.
Не то чтобы вам пришлось бы кодировать меньше, но это могло бы создать более чистый / поддерживаемый код.