I'm developing a Telegram bot. And I'm struggling with SimpleInjector class registration.
I need to register a class ProxyBotMesssageSender
as a proxy for BotMesssageSender
class that implements 3 interfaces: IMessageSender
,IMessageForwarder
,IMessageCopier
The whole structure:
//Provides sending of new messages
public interface IMessageSender{
IObservable<Message> ObservableSend(SendMessage message);
IObservable<Message> ObservableSend(MessageBuilder messageBuilder);
}
//Provides forwarding of existing messages
public interface IMessageForwarder{
IObservable<Message> Forward(ForwardMessage message);
IObservable<MessageIdObject[]> Forward(ForwardMessages messages);
}
//Provides copying of existing messages
public interface IMessageCopier{
IObservable<MessageIdObject> Copy(CopyMessage message);
IObservable<MessageIdObject[]> Copy(CopyMessages messages);
}
//Wrapper for third party library
public class BotMesssageSender : IMessageSender, IMessageForwarder, IMessageCopier{
public BotMesssageSender(TelegramBot bot)
{
this.bot = bot;
}
...
}
//Proxy that provides processing of sending limits
//It creates inner disposable object that provides information
//for the class when it's allowed to send next message
public class ProxyBotMesssageSender : AbstractBotMessageSender, IMessageSender, IMessageForwarder, IMessageCopier, IDisposable{
public BotMessageSenderTimerProxy(IMessageSender sender, IMessageCopier messageCopier, IMessageForwarder messageForwarder)
{
this.sender = sender;
this.messageCopier = messageCopier;
this.messageForwarder = messageForwarder;
...
}
}
My current registration instructions:
public override void Install()
{
Container.Register<IMessageSender,BotMesssageSender>(Lifestyle.Singleton);
Container.Register<IMessageForwarder,BotMesssageSender>(Lifestyle.Singleton);
Container.Register<IMessageCopier,BotMesssageSender>(Lifestyle.Singleton);
Container.RegisterDecorator<IMessageSender,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
Container.RegisterDecorator<IMessageForwarder,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
Container.RegisterDecorator<IMessageCopier,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
...
}
But as I expected, the injector tries to decorate each interface one by one. Is it possible to register proxy somehow, that injector will apply it as a decorator for the interfaces without breaking the DI principle?
Update for @Steven
void Method(){
var telegramBot = new TelegramBot();
var sender = new BotMesssageSender(telegramBot);
var generalSender = new BotMessageSenderProxy(sender, sender, sender);
IMessageSender messageSender = generalSender;
IMessageCopier messageCopier = generalSender;
IMessageForwarder messageForwarder = generalSender;
}
You are trying to use BotMessageSenderProxy
as a decorator, while it doesn't fit Simple Injector's definition of a decorator. This is because you are trying to register BotMessageSenderProxy
as decorator for three interfaces, while, from Simple Injector's perspective, a decorator class should only be used to decorate on specific interface.
In your current design and registration, that would lead to a cyclic dependency, as the other two interfaces would receive the decorator as well. This is what the object graph would look like when resolving the IMessageSender
:
var bot = new TelegramBot();
var botSender = new BotMesssageSender(bot);
IMessageSender sender =
new BotMessageSenderTimerProxy(
sender: botSender,
copier: new BotMessageSenderTimerProxy(
sender: new BotMessageSenderTimerProxy(
sender: botSender,
copier: new BotMessageSenderTimerProxy(
sender: new BotMessageSenderTimerProxy(
sender: botSender,
copier: new BotMessageSenderTimerProxy(
sender: new BotMessageSenderTimerProxy(
sender: botSender,
copier: new BotMessageSenderTimerProxy(
sender: new BotMessageSenderTimerProxy(
sender: botSender,
copier: new BotMessageSenderTimerProxy(...),
...
As this dependency graph is cyclic, it has no end. It would go on in depth forever. That that's clearly not a pursuable goal.
Instead, I suggest changing your BotMessageSenderTimerProxy
into a proxy on merely the BotMesssageSender
. In other words, change its definition to the following:
public class ProxyBotMesssageSender : AbstractBotMessageSender
, IMessageSender, IMessageForwarder, IMessageCopier, IDisposable
{
public BotMessageSenderTimerProxy(BotMesssageSender sender)
{
this.sender = sender;
}
...
}
When you do this, you can change your registrations to the following:
Container.Register<TelegramBot>();
Container.Register<BotMesssageSender>();
Container.Register<IMessageSender, BotMessageSenderProxy>();
Container.Register<IMessageCopier, BotMessageSenderProxy>();
Container.Register<IMessageForwarder, BotMessageSenderProxy>();
This construct the following object graphs:
var bot = new TelegramBot();
var botSender = new BotMesssageSender(bot);
var proxy = new BotMessageSenderProxy(botSender);
IMessageSender sender = proxy;
IMessageCopier copier = proxy;
IMessageForwarder forwarder = proxy;