Инверсия контроля и инъекция зависимостей - PullRequest
4 голосов
/ 09 января 2012

Вот одна из самых обсуждаемых концепций: Ioc (инверсия контроля). Я уже давно использую эту концепцию. Обычно я использую DI (а не сервисный локатор) для реализации IoC в моем коде. Ниже мое понимание IoC.

Если classA зависит от classB, если он объявляет экземпляр classB внутри него. Это зависит от класса B, по какой-то причине (я вернусь к этому позже), не очень хорошо, и поэтому мы используем DI, чтобы это исправить. Итак, теперь у нас есть интерфейс под названием IClassB, который будет передан в конструктор класса A (здесь я расскажу о внедрении конструктора). Вот код:

public class ClassA
{
    IClassB _classBObj = null;

    public ClassA(IClassB classBObj)
    {
         _classBObj = classBObj;
    }

    public void UseClassBMethod()
    {
        this._classBObj.classBMethod();
    }
}

public class ClassB:IClassB
{

    public void classBMethod()
    {
        //Some task carried out by classB.
    }
}

public interface IClassB
{
    void classBMethod();
}

И вот код, который заставляет его работать:

class Program
{
    static void Main(string[] args)
    {
        //This code is outside of class A and hence declaring 
        //object of ClassB is not dependency
        ClassA classA=new ClassA(new ClassB);
        classA.UseClassBMethod();

    }
}

Я продемонстрировал приведенный выше пример только для того, чтобы убедиться в правильности моего понимания IoC и DI. Если вы нашли что-то не так, пожалуйста, поправьте меня. Теперь, когда я попытался выяснить, почему IoC, я нашел две важные причины:

  1. Тестируемость: Конечно, правильная проблема. Предположим, что в приведенном выше примере, например, метод classB использует SMPTP-сервер или какой-либо WCF / Webservice, мы не сможем его протестировать. Однако мы можем создать класс тестовой заглушки, реализующий интерфейс IClassB, и продолжить тестирование, передав экземпляр класса тестовой заглушки.

  2. Зависимость с точки зрения конкретной имплементации: это то, с чем я не мог ужиться. Даже если я изменю код класса A следующим кодом:

public class ClassA
{
   ClassB _classBObj = null;

   public ClassA()
   {
     _classBObj = new ClassB();
   }

   public void UseClassBMethod()
   {
      this._classBObj.classBMethod();
   }
}

Как IoC помогает избавиться от любой проблемы, возникающей из-за изменения в методе classB объекта ClassB? Если есть какое-либо изменение в сигнатуре метода, мы должны будем также внести это изменение в сигнатуру метода интерфейса IClassB (это фактически увеличивает задачу рефакторинга.). Если реализация метода изменится, скажем, например, что она выполняет дополнительную задачу регистрации, изменение будет ограничено кодом ClassB.

Я могу показаться немного глупым с вопросом, но может ли кто-нибудь представить сценарий, в котором (кроме тестируемости модулей) мы получаем пользу от этого подхода?

Большое спасибо за чтение.

Ответы [ 2 ]

6 голосов
/ 09 января 2012

Основная идея здесь в том, что вы program against an interface.
Ваш код не знает ни о каких конкретных классах.

В результате вы можете переключать конкретные реализации, не влияя на ваш код.

В вашем случае вы могли бы заменить ClassA classA=new ClassA(new ClassB); на ClassA classA=new ClassA(new ClassC);, который имеет другое поведение, например, менее или более оптимален или требует некоторого пароля, чтобы что-то сделать и т. Д.

Идея состоит в том, что если ClassC придерживается интерфейса, который ClassB также реализует, тогда вы можете перейти на использование ClassC без каких-либо изменений в вашем коде

Если интерфейс изменился, конечно, ваш код затронут.Этого вы не можете избежать.

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

2 голосов
/ 09 января 2012

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

В некоторых реальных сценариях вы можете в конечном итоге внедрить множество экземпляров в конструктор, а иногда, в зависимости от логики приложения, лишь немногие из них фактически используются в течение жизненного цикла объекта.Это означает, что иногда вы инициализируете зависимость, даже если она не используется.

В .NET 4 вы можете использовать класс Lazy, который позволяет создавать экземпляр класса зависимостей только при его первом использовании.Здорово, когда создание экземпляров зависимостей занимает много времени или экземпляр требует много памяти.

public class ClassA
{
    private readonly Lazy<IClassB> _lazyClassBObj;

    public ClassA(Lazy<IClassB> lazyClassBObj)
    {
      _lazyClassBObj = lazyClassBObj;        
    }

    public void UseClassBMethod()
    {
        _lazyClassBObj.Value.classBMethod();
    }
}

class Program
{
    static void Main(string[] args)
    {
        ClassA classA = new ClassA (new Lazy<IClassB>(() => new ClassB));
        ...

    }
}

Другая хитрость заключается в том, чтобы внедрить делегат фабрики в конструктор вместо экземпляра.Это очень похоже на решение Lazy, но у него есть несколько преимуществ.Инъекция фабричного делегата - это здорово, если ваш класс должен иметь возможность создавать любое количество новых экземпляров класса зависимости (хотите создавать экземпляры в цикле или что-то подобное).В этом примере ClassA будет иметь ссылку на метод, который может создать объект, который реализует IClassB.Чтобы сделать вещи более интересными, ClassB, который реализует IClassB, также имеет зависимость IClassC.

public class ClassA
{
    private readonly Lazy<IClassC> _lazyClassBObj;
    private readonly Func<IClassC, IClassB> _classBObjFactory;

    public ClassA(Func<IClassC, IClassB> classBObjFactory, Lazy<IClassC> lazyClassBObj)
    {
      _classBObjFactory = classBObjFactory;
      _lazyClassBObj = lazyClassBObj;        
    }

    public void UseClassBMethod()
    {
        var classC = _lazyClassBObj.Value;

        var classB1 = _classBObjFactory(classC); // instance 1
        var classB2 = _classBObjFactory(classC); // instance 2
        var classB3 = _classBObjFactory(classC); // instance 3
    }
}

Основным преимуществом этого подхода является снижение требований к памяти за счет отказа от инициализации объектов, которые вы, возможно, не используете.Преимущество внедрения фабричного делегата состоит в том, что вы можете скрыть логику инициализации зависимостей и можете контролировать, какие фабричные параметры будут отображаться для вызывающей стороны.ClassA не нужно знать, как собрать все свои зависимости, ему нужно знать только те параметры, о которых он знает, и которыми он может управлять.Это позволяет вам заменить зависимости более легко.

Надеюсь, это имеет смысл :) Просто хотел продемонстрировать, как можно получить больше от паттерна, используя функциональный стиль C #.

...