Какую роль играют делегаты в внедрении зависимостей? - PullRequest
8 голосов
/ 08 октября 2009

В большинстве примеров внедрения зависимостей я вижу, как вводятся простые объекты , например, в приведенном ниже примере 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 ]

4 голосов
/ 08 октября 2009

Иногда я использую делегатов в качестве Анонимных интерфейсов - также для DI.

Однако одна из проблем этого подхода заключается в том, что модульному тестированию становится немного сложнее, если в класс вводится и используется правильная зависимость, поскольку экземпляр делегата не является типом, а иногда просто хочу убедиться, что класс использует правильный тип стратегии / зависимости.

3 голосов
/ 28 августа 2013

Возвращаясь к объектно-ориентированным принципам, одна из ключевых особенностей объекта - его поведение и состояние . Я мог бы представить сценарий, в котором обработчику журнала может потребоваться поддерживать какое-то состояние (имя файла журнала, соединение с БД и т. Д.), Но также может быть аргумент для обработчика журнала, которому не нужно заботиться о состоянии.

Если вашей зависимости нужно управлять собственным состоянием, используйте подходящий объект (скорее интерфейс).

Если ваша зависимость имеет только поведение, а не состояние, тогда может подойти делегат, хотя некоторым людям может быть удобнее использовать надлежащий объект (интерфейс) в любом случае, так как позже было бы легче добавить к нему управление состоянием, если необходимо.

Преимущество делегатов в том, что они CRAZY просты для насмешки с помощью лямбда-выражений :) (хотя интерфейсы тоже довольно просты для насмешки)

Теперь, конечно, любой делегат все еще может быть каким-то нормальным методом для некоторого нормального объекта, и этот метод может полностью иметь поведение, которое влияет на состояние объекта, и, безусловно, есть веские причины для этого, но вы приближаетесь точка, в которой может иметь смысл просто взять зависимость от всего объекта, а не только от одного из его методов.

Далее по этому пути внедрение делегатов также может быть способом применения Принцип разделения интерфейса , так что вы можете убедиться, что ваша система не зависит от того, что она не использует.

Еще одно замечание о делегатах ...

Почти никогда нет веской причины определять свой собственный тип делегата. Большинство вариантов использования соответствуют типам 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 голосов
/ 08 октября 2009

Я знаю, что MEF , например, позволяет вводить делегатов. Однако вы также можете создать интерфейс ILog, который имеет метод Log с той же сигнатурой, что и у вашего делегата. Я думаю, будет гораздо понятнее понять, что целью было внедрение реализации объекта, способного регистрировать, а не одной функции журнала.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...