Edw*_*uay 10 c# delegates dependency-injection
在大多数依赖注入的例子中,我看到了注入的简单对象,例如在下面的示例中,SecurityManager被注入到MainApplication中.
但是,注入委托似乎也很自然,如下面的示例中LogHandler注入到MainApplication中.
代表一般不用于依赖注入吗?他们使用的原因是什么?
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");
            }
        }
    }
}
回到面向对象的原则,对象的关键特征之一是它具有行为和状态。我可以想象这样一个场景:日志处理程序可能需要维护某种状态(日志文件名、数据库连接等),但也可能存在日志处理程序不需要关心状态的争论。
如果您的依赖项需要管理自己的状态,请使用适当的对象(而不是接口)。
如果您的依赖项只有行为而没有状态,那么委托可能是合适的,尽管有些人可能更愿意使用正确的对象(接口),因为稍后如果需要的话向其添加状态管理可能会更容易。
委托的一个好处是用 lambda 表达式来模拟它们非常简单:)(尽管接口也很容易模拟)
当然,现在任何委托仍然可以只是某个普通对象上的一些普通方法,并且该方法完全可以具有影响对象状态的行为,并且肯定有充分的理由这样做,但是您正在接近这样的地步:仅依赖整个对象而不是仅依赖其方法之一可能更有意义。
沿着这条路走下去,注入委托也可以成为应用接口隔离原则的一种方法,这样您就可以确保您的系统不依赖于它不使用的东西。
几乎没有充分的理由来定义自己的委托类型。大多数用例都适合Func<>和Action<>C# 类型(和事件,但这是另一个问题)。在您的情况下,您的MainApplication构造函数不应采用 aWindow1.LogHandler作为参数,而应仅采用Action<string>. 然后你只需这样调用它:
MainApplication app = new MainApplication(ConsoleLogHandler, new ToughSecurityManager());
或类似的,因为该ConsoleLogHandler方法已经符合Action<string>签名。
在您的测试中,您只需将其实例化:
MainApplication app = new MainApplication(x => { /*Do nothing*/ }, new MySecurityManagerStub());
甚至更好:
int timesCalled;
MainApplication app = new MainApplication(x => { timesCalled++ }, new MySecurityManagerStub());
然后,您可以验证 MainApplication 调用该方法的次数是否与您预期的次数完全相同。
例如,我知道MEF允许注入委托。但是,您也可以创建一个 ILog 接口,该接口具有与您的委托具有相同签名的 Log 方法。我认为这样会更清楚地理解,其目的是注入能够记录日志的对象的实现,而不是单个日志函数。