In most examples of dependency injection, I see simple objects being injected, such as in the example below SecurityManager gets injected into MainApplication.
However, it would seem natural to inject delegates as well, as in the example below LogHandler gets injected into MainApplication.
Are delegates generally not used in dependency injection? What would be reasons for and against their use?
using System;
using System.Windows;
using System.Windows.Controls;
namespace TestSimpleDelegate82343
{
public partial class Window1 : Window
{
public delegate void LogHandler(string message);
public Window1()
{
InitializeComponent();
}
private void Button_Gui_Lax_Click(object sender, RoutedEventArgs e)
{
MainApplication app = new MainApplication(new LogHandler(GuiLogHandler), new LaxSecurityManager());
}
private void Button_Console_Lax_Click(object sender, RoutedEventArgs e)
{
MainApplication app = new MainApplication(new LogHandler(ConsoleLogHandler), new LaxSecurityManager());
}
private void Button_Gui_Tough_Click(object sender, RoutedEventArgs e)
{
MainApplication app = new MainApplication(new LogHandler(GuiLogHandler), new ToughSecurityManager());
}
private void Button_Console_Tough_Click(object sender, RoutedEventArgs e)
{
MainApplication app = new MainApplication(new LogHandler(ConsoleLogHandler), new ToughSecurityManager());
}
public void GuiLogHandler(string message)
{
TextBlock tb = new TextBlock();
tb.Text = "logging: " + message;
TheContent.Children.Add(tb);
}
public void ConsoleLogHandler(string message)
{
Console.WriteLine("logging: " + message);
}
}
public interface ISecurityManager
{
bool UserIsEntitled();
}
public class LaxSecurityManager : ISecurityManager
{
public bool UserIsEntitled()
{
return true;
}
}
public class ToughSecurityManager : ISecurityManager
{
public bool UserIsEntitled()
{
return false;
}
}
public class MainApplication
{
public MainApplication(Window1.LogHandler logHandler, ISecurityManager securityManager)
{
logHandler("test1");
logHandler("test2");
logHandler("test3");
if (securityManager.UserIsEntitled())
{
logHandler("secret");
}
}
}
}
I occasionally use delegates as Anonymous Interfaces - also for DI.
One issue with this approach, however, is that it becomes a little bit more difficult to unit test that the correct Dependency was injected and used in a class, because a delegate instance isn't a type, and sometimes you'd simply just want to verify that a class uses the correct type of Strategy/Dependency.
Going back to object oriented principles, one of the key features of an object is that it has behaviour and state. I could envision a scenario where a log handler might need to maintain some sort of state (logfilename, db connection, etc.), but there might also be an argument for a log handler not needing to concern itself with state.
If your dependency needs to manage state of its own, use a proper object (rather, an interface).
If your dependency has only behaviour and not state, then a delegate might be suitable, although some people might be more comfortable using a proper object (interface) anyway, as it might be easier to add state management to it later on if needed.
A benefit of delegates is that they're CRAZY simple to mock with lambda expressions :) (even though interfaces are pretty easy to mock, too)
Now of course any delegate can still just be some normal method on some normal object, and that method can totally have behaviour that affects the state of the object, and there are certainly valid reasons to do that, but you're approaching the point where it might make more sense just to take a dependency on the whole object, instead of just one of its methods.
Further down this path, injecting delegates can also be a way to apply Interface Segregation Principle, so you can make sure your system isn't dependent on things it doesn't use.
There's almost never a good reason to define your own delegate type. Most of the use cases fit into the Func<>
and Action<>
C# types (and events, but that's another issue).
In your case, your MainApplication
constructor should not take a Window1.LogHandler
as a parameter, but instead just an Action<string>
. Then you'd just call it with:
MainApplication app = new MainApplication(ConsoleLogHandler, new ToughSecurityManager());
or similar, since the ConsoleLogHandler
method already fits the Action<string>
signature.
And in your test, you'd just instanciate it with:
MainApplication app = new MainApplication(x => { /*Do nothing*/ }, new MySecurityManagerStub());
or even better:
int timesCalled;
MainApplication app = new MainApplication(x => { timesCalled++ }, new MySecurityManagerStub());
Then you can verify that MainApplication called the method exactly as many times as you intended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With