代表在依赖注入中扮演什么角色?

时间:2009-10-08 09:53:38

标签: c# dependency-injection delegates

在大多数依赖注入示例中,我看到注入了简单的对象,例如下面的示例 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");
            }
        }
    }

}

3 个答案:

答案 0 :(得分:4)

我偶尔会将代表作为Anonymous Interfaces使用 - 也适用于DI。

然而,这种方法的一个问题是单元测试变得有点困难,因为委托实例不是类型,有时候你会在类中注入和使用正确的Dependency。只是想验证一个类使用正确类型的策略/依赖关系。

答案 1 :(得分:3)

回到面向对象的原则,对象的一个​​关键特征是它具有行为和状态。我可以想象一个日志处理程序可能需要维护某种状态(logfilename,db connection等)的场景,但是日志处理程序可能还有一个参数,不需要关注状态。

如果您的依赖项需要管理自己的状态,请使用适当的对象(而不是接口)。

如果您的依赖项只有行为而不是状态,那么委托可能是合适的,尽管有些人可能更习惯使用正确的对象(接口),因为稍后可能更容易向其添加状态管理。需要的。

代表们的一个好处是,他们很难用lambda表达式来模拟:)(尽管界面也非常容易模拟)

现在当然任何委托仍然可以只是一些普通对象的常规方法,并且该方法可以完全具有影响对象状态的行为,并且肯定有正当理由这样做,但是你正在接近只需对整个对象进行依赖,而不仅仅是其中一种方法,这可能更有意义。

在此路径中,注入代理也可以是一种应用Interface Segregation Principle的方法,因此您可以确保您的系统不依赖于它不使用的内容。

关于代表的另一个注意事项......

几乎没有充分的理由来定义自己的委托类型。大多数用例都适合Func<>Action<> C#类型(和事件,但这是另一个问题)。 在您的情况下,您的MainApplication构造函数不应将Window1.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是否完全按照您的意图调用该方法。

答案 2 :(得分:2)

我知道MEF例如允许注入代理。但是,您也可以创建一个ILog接口,该接口具有与您的委托具有相同签名的Log方法。我认为理解打算是一个能够记录而不是单个日志函数的对象的实现会更清楚。