Вам нужно сделать несколько твиков:
1. Вам нужно IMessage
?
Если у вас есть несколько реализаций сообщений очереди, то, в противном случае, если сообщение содержит только данные, возможно, этот интерфейс может быть опущен.
2. Отделите MessageQueueManager
от Message
Если Message
использует MessageQueueManager
, то с внедрением зависимости вы должны начать передавать MessageQueueManager
сообщениям при каждом создании. Предполагая, что у вас может быть много мест, где сообщения создаются без ведома администратора очередей, передача MessageQueueManager
в качестве параметра может стать серьезным изменением. И вы можете избежать этого.
Давайте предположим, что когда сообщение может быть отклонено, оно должно быть уже в одной или нескольких очередях. В этом случае мы могли бы делегировать действие dismiss очередям , в которые было добавлено сообщение, и имеет смысл, чтобы очереди сообщений знали своего manager .
Итак, вам нужно что-то вроде этого:
public class Message : IMessage
{
public Message(string text, Action onDismiss) { }
// This is set by MessageQueue when the message is being added to the queue
internal IMessageQueue Queue { get; set; }
public void Dismiss()
{
//_onDismiss();
if (Queue == null)
{
throw new InvalidOperationException("Message not queued.");
}
Queue.DismissMessage(this);
}
}
3. MessageQueue
создание
MessageQueue
создание должно быть централизовано. Это делается для того, чтобы после создания экземпляра очереди вы могли установить MessageQueueManager
, чтобы очередь могла обрабатывать NotifyMessageQueued
и MessageDismissed
.
Как то, что вы уже пробовали (внедрение queueConstructor
), вам нужно, как говорили другие,
использовать фабрику.
Эта фабрика здесь должна быть вашей собственной фабрикой согласно принципу обращения зависимостей . Вы должны воздерживаться от использования любой фабрики DI, специфицированной c factory, как ILifetimeScope
в Autofa c. Это потому, что внедрение зависимостей является инструментом и должно быть необязательным. Вам нужна инверсия зависимостей больше, чем внедрение зависимостей. Так что я бы рекомендовал вам не ie вашими типами один DI framework.
public interface IMessageQueueFactory
{
// Those are shotcuts to CreatePriority(N)
IMessageQueue CreateLowPriority();
IMessageQueue CreateHighPriority();
IMessageQueue CreatePriority(int priority);
}
// Forgive the verbosal name here. It indicates the factory creates instance of MessageQueue.
public class MessageQueueMessageQueueFactory : IMessageQueueFactory
{
private Lazy<IMessageQueueManager> _manager;
// The reason for using Lazy<T> here is becuase we have circular dependency in this design:
// MessageQueueManager -> MessageQueueMessageQueueFactory -> MessageQueueManager
// No all DI framework can handle this, so we have to defer resolving manager instance
// Also because queue manager is a singleton instance in the container, so deferred resolving
// doesn't cause much pain such as scoping issues.
public MessageQueueMessageQueueFactory(Lazy<IMessageQueueManager> manager)
{
_manager = manager;
}
public IMessageQueue CreateHighPriority()
{
return CreatePriority(1);
}
public IMessageQueue CreateLowPriority()
{
return CreatePriority(2);
}
public IMessageQueue CreatePriority(int priority)
{
return new MessageQueue(priority, _manager);
}
}
Это нормально, если вам нужно внедрить спецификацию framework c factory в MessageQueueMessageQueueFactory
потому что здесь предполагается, что это адаптер, преобразующий ваше собственное создание типа в разрешение типа DI.
И MessageQueue
теперь выглядит так:
public class MessageQueue : IMessageQueue
{
private int _priority;
private Lazy<IMessageQueueManager> _manager;
// Use internal constructor to encourage using factory to instantiate message queue.
internal MessageQueue(int priority, Lazy<IMessageQueueManager> manager)
{
_priority = priority;
_manager = manager;
}
public void QueueMessage(IMessage msg)
{
_queue.Add(msg);
((Message)msg).Queue = this;
_manager.Value.NotifyMessageQueued(this);
}
public void DismissMessage(IMessage msg)
{
// So we have a type cast here. Depends on your message queue implemenation,
// this is ok or not.
// For example, if this is a RabbitMQ message queue, of course the message
// instance must also be a RabbitMQ message, so cast is fine.
// However, if the message queue is a base class for other message queues, this
// should be avoided.
// And this also goes back to the point: Do you really need an IMessage interface?
if (((Message)msg).Queue != this)
{
throw new InvalidOperationException("Message is not queued by current instance.");
}
_manager.Value.MessageDismissed(this);
}
}
4. Создание очереди сообщений в MessageQueueManager
Теперь у нас есть фабрика для создания экземпляров очередей сообщений, мы можем использовать ее в конструкторе MessageQueueManager
:
public class MessageQueueManager : IMessageQueueManager
{
private readonly IMessageQueue _highPriority;
private readonly IMessageQueue _lowPriority;
public MessageQueueManager(IMessageQueueFactory queueFactory)
{
_highPriority = queueFactory.CreateHighPriority();
_lowPriority = queueFactory.CreateLowPriority();
}
}
Обратите внимание, что поскольку мы хотим использовать Внедрение зависимости, и MessageQueueManager
теперь использует IMessageQueueFactory
, поэтому нам нужно изменить его с реального одноэлементного на одноэлементный DI, удалив свойство Instance
и зарегистрировав тип как singleton
5. Вам нужно обработать семейство очередей?
Скажем, если у вас есть несколько реализаций очереди, вы, вероятно, не хотите смешивать типы очередей c. Например, вы не хотите добавлять сообщение RabbitMQ в очередь MSMQ. В этом случае вы можете использовать Абстрактный заводской шаблон :
public interface IMessageQueueProvider
{
IMessageQueueManager CreateManager();
IMessageQueueFactory CreateFactory();
IMessage CreateMessage();
}
Но это немного выходит за рамки.
Наконец
Зарегистрируйте новые типы. Я использую Microsoft Extensions Dependency Injection в качестве примера:
var services = new ServiceCollection();
services.AddSingleton<IMessageQueueManager, MessageQueueManager>();
services.AddSingleton<IMessageQueueFactory, MessageQueueMessageQueueFactory>();
// Most DI containers now should support Lazy<T>, in case if it is not, you could add
// explicit registration.
services.AddTransient(
provider => new Lazy<IMessageQueueManager>(
() => provider.GetRequiredService<IMessageQueueManager>()));
// This provides a way to resolve low priority queue as IMessageQueue when injected into
// other types.
services.AddTransient(
provider => provider.GetRequiredService<IMessageQueueFactory>().CreateLowPriority());
Вот несколько примеров:
class Examples
{
public Examples(
// This is the queue manager
IMessageQueueManager manager,
// This is the queue factory for creating queues
IMessageQueueFactory factory,
// This is default queue implemenation, in this case a low priority queue
IMessageQueue queue)
{
// This is queue with arbitrary priority.
var differentPriorityQueue = factory.CreatePriority(3);
// Queue a message and dismiss.
var msg = new Message("Text", () => { });
queue.QueueMessage(msg);
msg.Dismiss();
}
}