Я смотрю на внедрение зависимостей

задняя часть Шаблоны проектирования

новый код запах - ся ни отношения: слишком близко

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

Что такое запах кода?

  • Если в каком-то фрагменте кода может быть проблема, можно сказать, что он имеет запах кода. «Может быть» используется здесь, потому что небольшой запах кода не обязательно является проблемой.
  • Запахи кода также могут указывать на наличие технического долга, а его устранение требует затрат. Чем дольше у вас есть технический долг, тем сложнее будет его исправить.
  • Есть много категорий запахов кода.

Подумайте, почему, за исключением некоторых особых случаев, каждое место, где появляется новое ключевое слово, является запахом кода?

Пример, показывающий, как нарушить адаптивность кода путем создания экземпляров объектов.

public class AccoutController
{
    private readonly SecurityService securityService;

    public AccountController()
    {
        this.securityService = new SecurityService();
    }

    [HttpPost]
    public void ChangePassword(long userId,string newPassword)
    {
        var userRepository = new UserRepository();
        var user = userRepository.GetById(userId);
        this.securityService.ChangePassword(user,newPassword);
    }
}

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

  • Класс AccoutController всегда зависит от конкретной реализации класса SecurityService и класса UserRepository.
  • Класс AccoutController неявно зависит от класса SecurityService и всех зависимостей класса UserRepository.
  • Класс AccoutController сложно тестировать, потому что нет возможности имитировать и заменить классы SecurityService и UserRepository поддельными реализациями (фиктивными объектами или заглушками).
  • Метод ChangePassword класса SecurityService требует от клиента предварительного выбора и загрузки экземпляра объекта класса User (замаскированная зависимость).

Детально проанализируйте эти вопросы. 1. Невозможно улучшить реализацию - нарушение принципа OCP open-closed Когда мы хотим изменить реализацию класса SecurityService, есть только два варианта, либо изменить AccountController так, чтобы он напрямую ссылался на новую реализацию, либо добавить новую функции к существующему классу SecurityService. Мы обнаружим, что ни один из вариантов не является хорошим. Первый вариант нарушает открытый-закрытый принцип закрытости для модификации и открытости для расширения; второй может нарушать принцип единой ответственности SRP. Такой код не может улучшить реализацию и равносилен одноразовой сделке. 2. Цепочка зависимостей - нарушение принципа DIP-инверсии управления Класс AccoutController зависит от класса SecurityService, а класс SecurityService также будет иметь свои зависимости. Приведенный выше пример кода может выглядеть так, как будто класс SecurityService не имеет зависимостей, но на самом деле он может выглядеть так:

public SecurityService()
{
    this.Session = SessionFactory.GetSession();
}

Класс SecurityService фактически полагается на SessionFactory для получения объекта Session, что означает, что класс AccoutController также неявно зависит от SessionFactory. Нарушает принцип DIP-инверсии управления: модули более высокого уровня не могут зависеть от модулей более низкого уровня, оба должны зависеть от абстрактных интерфейсов или абстрактных классов. И код примера полон зависимостей от низкоуровневых модулей.

3. Отсутствие тестируемости - нарушение тестируемости кода Тестируемость кода тоже очень важна, она требует сборки кода в определенном формате. Если вы этого не сделаете, тестирование станет чрезвычайно сложным. Мы должны знать, что мы написали модульные тесты.Первый шаг модульного тестирования - это изоляция зависимостей тестовых объектов.Только таким образом наши тесты стабильны (исключая нестабильность зависимых объектов) и воспроизводимы. Moq фреймворка изоляции, который мы используем (фактически все фреймворки изоляции), выполняется черезИспользуйте фиктивные реализации для замены зависимостей тестируемого объектаРабота. Зависимые объекты в примере кода были определены на этапе компиляции кода, и зависимые объекты не могут быть динамически заменены на этапе выполнения кода, поэтому они не подлежат тестированию.

Альтернативы объектному строительству

Что можно сделать, чтобы улучшить классы AccountController и SecurityService или любые другие неподходящие вызовы построения объектов? Как правильно спроектировать и реализовать эти два класса, чтобы избежать проблем, описанных в предыдущем разделе? Ниже приведены некоторые дополнительные подходы на выбор. 1. Программирование с учетом интерфейса Первое изменение, которое нам нужно сделать, это скрыть реализацию класса SecurityService за интерфейсом. Таким образом, класс AccountController будет полагаться только на интерфейс класса SecurityService, а не на его конкретную реализацию. Первый рефакторинг кода заключается в извлечении интерфейса для класса SecurityService. Извлеките интерфейс для класса SecurityService:

public interface ISecurityService
{
    void ChangePassword(long userId,string newPassword);
}

public class SecurityService:ISecurityService
{
    public void ChangePassword(long userId,string newPassword)
    {
        //...
    }
}

Следующим шагом является изменение класса клиентского кода для вызова интерфейса ISecurityService вместо класса SecurityService. Класс AccountController теперь зависит от интерфейса ISecurityService:

public class AccountController
{
    private readonly ISecurityService securityService;

    public AccountController ()
    {
        this.securityService = new SecurityService();
    }

    public void ChangePassword(long userId,string newPassword)
    {
        securityService.ChangePassword(userId,newPassword);
    }
}

Рефакторинг еще не закончен, потому что конструктор класса SecurityService по-прежнему вызывается напрямую, поэтому рефакторинговый класс AccountController по-прежнему зависит от конкретной реализации класса SecurityService. Чтобы полностью разделить эти два конкретных класса, требуется дальнейший рефакторинг. Введение внедрения зависимостей (DI). 2. Использование внедрения зависимостей Эта тема слишком велика, чтобы ее можно было осветить в коротком месте. И мы подробно обсудим внедрение зависимостей позже, поэтому сейчас я начну только сКлассы, использующие внедрение зависимостейпояснить некоторые основные моменты. Продолжая наш рефакторинг, рефакторинговая часть кода конструктора выделена жирным шрифтом, изменения в действии рефакторинга очень малы, но возможности управления зависимостями сильно отличаются. Класс AccountController больше не требует создания экземпляра класса SecurityService, а вместо этого требует, чтобы его клиентский код предоставлял реализацию интерфейса ISecurityService. Используйте внедрение зависимостей, чтобы удалить зависимость от класса SecurityService из класса AccountController:

public class AccountController
{
    private readonly ISecurityService securityService;

    
    public AccountController (ISecurityService securityService)
    {
        if(securityService == null)
        {
            throw new ArgumentNullException("securityService");
        }
        this.securityService = securityService;
    }
    

    public void ChangePassword(long userId,string newPassword)
    {
        this.securityService.ChangePassword(userId,newPassword);
    }
}

В этом разделе мы в основном обсуждаемзапах нового кодаИ его недостатки, это также приводит к двум взаимодополняющим способам запаха нового кода путем рефакторинга кода:Кодирование и использование внедрения зависимостей для интерфейсов. Почему ты говоришьдополнительныйПричина в том, что код для интерфейса может лишь частично развязать код, либо не решает проблему прямого вызова конструктора зависимого класса; хотя использование внедрения зависимостей решает эту проблему, но использование внедрения зависимостей зависит по программированию интерфейса.Можно сказать, что только когда мы кодируем интерфейс, можно использовать внедрение зависимостей для устранения запаха нового кода..

Забудьте, кто это сказал, поймите историю изучения чего-либо, прежде чем узнать об этом. Чтобы усвоить любое знание, очень важно научиться образу мышления, способу мышления о проблемах и способу мышления для решения проблем. Поэтому я надеюсь использовать очень простую маленькую игру, чтобы описать эволюцию внедрения зависимостей и то, что способствовало развитию внедрения зависимостей. Я надеюсь, что каждый сможет получить что-то полезное после прочтения, и я надеюсь, что у каждого будет собственное понимание внедрения зависимостей после прочтения. Давайте начнем!

утиная кошачья драка

好了,让我们从最简单的开始,希望我们能从简单到复杂,慢慢理解从面向接口编程到依赖注入的思想:

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

public abstract class Duck
{
	public void Eat(){};
    public void Run(){};
    public abstract void Display();
}

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

public class BeijingDuck:Duck
{
    public override void Display()
    {
        //北京鸭
    };
}

public class ShandongDuck:Duck
{
    public override void Display()
    {
        //山东鸭
    };
}

    //其他鸭...

Ну вот и вышла первая версия Duck Duck Wars. Теперь, когда продукт хочет вызывать уток в игре, самый простой способ реализовать это — добавить метод Shout() в абстрактный базовый класс, чтобы можно было вызывать все унаследованные типы уток. Но скоро мы узнаем, что проблема грядет, и все утки залают, что явно нелогично. Тогда кто-то обязательно подумает об использовании интерфейса, извлечет метод Shout() в интерфейс, а затем позволит типу утки, который может вызвать, реализовать интерфейс.

public interface IShout
{
    void Shout();
}

public class BeijingDuck:Duck,IShout
{
    public override void Display()
    {
        //北京鸭
    };

    public void Shout()
    {
        //呱呱
    }
}

public class ShandongDuck:Duck,IShout
{
    public override void Display()
    {
        //山东鸭
    };

    public void Shout()
    {
        //呱呱
    }
}

Вышеуказанное реализация выглядит хорошо, но, но, но, но требования всегда меняются.

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

Поймите, почему «программируйте интерфейс, а не реализацию»

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

public interface IShout
{
    void Shout();
}

public class GuaGuaShout:IShout
{
    public void Shout()
    {
        //呱呱
    }
}

public class GaGaShout:IShout
{
    public void Shout()
    {
        //嘎嘎
    }
}

    //其他叫声行为类

Теперь конкретная реализация типа утки становится:

public class BeijingDuck:Duck
{
    IShout shout;
    public BeijingDuck(IShout shout)
    {
        this.shout = shout;
    }

    public void Shout()
    {
        shout.Shout();
    }
    //可以在运行时动态改变行为
    public void SetShout(IShout shout)
    {
        this.shout = shout;
    }

    public override void Display()
    {
        //北京鸭
    };
}

Преимущество такого дизайна заключается в том, что поведение может быть изменено динамически во время выполнения, и изменение поведения может быть добавлено, не затрагивая другие классы. Таким образом, эта конструкция полна гибкости. Сравнивая предыдущий дизайн, мы обнаружим, что предыдущий дизайн основан на наследовании абстрактных классов и реализации интерфейсов Оба этих дизайна основаны на «реализации» Поведение объекта было определено в момент компиляции и не может быть изменено. (композиция лучше, чем наследование)

Поймите, почему «полагайтесь на абстракции, а не на конкретные классы».

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

//创建一只会呱呱叫的北京鸭
new BeijingDuck(new GuaGuaShout());
//创建一只会嘎嘎叫的北京鸭
new BeijingDuck(new GaGaShout());
//创建一只会嘎嘎叫的北京鸭
new ShandongDuck(new GuaGuaShout());

Проблема возникает снова, код наполнен большим количеством «нового» кода. Когда мы используем «новый», мы создаем конкретный класс. Когда есть классы сущностей, код будет менее «эластичным». Чем оно более негибкое, тем труднее его трансформировать. Мы продолжим обсуждение «запаха нового кода» позже.

простая фабрика

Вернемся к игре. Чтобы увеличить интерактивность игры, вы можете выбрать утку или кошку, чтобы начать игру. Если мы выберем персонажа-утку, чтобы начать игру, то мы должны встретить фиксированную кошку в фиксированной сцене. Вы встретите персидских кошек в Персии, циветт в Китае и норвежских лесных кошек в Европе. Это не сложно реализовать с помощью простой фабрики.

public interface ICat
{   
    void Scratch();
}
//波斯猫
public class PersianCat:ICat
{
    public void Scratch()
    {
        //来自波斯猫的挠
    }
}
//狸花猫
public class FoxFlowerCat:ICat
{
    public void Scratch()
    {
        //来自狸花猫的挠
    }
}
//挪威森林猫
public class NorwegianForestCat:ICat
{
    public void Scratch()
    {
        //来自挪威森林猫的挠
    }
}

Фабрики, производящие кошек:

public class CatFactory
{
    public ICat GetCat(string catType)
    {
        if(catType.IsNullOrEmpty())
        {
            return null;
        }
        if(catType == "PersianCat")
        {
            return new PersianCat();
        }else if(catType == "FoxFlowerCat")
        {
            return new FoxFlowerCat();
        }else if(catType == "NorwegianForestCat")  
        {
            return new NorwegianForestCat();
        } 
        return null;
    }
}

Используйте фабрику для создания объекта кошки:

public class FactoryPatternDemo 
{
    public static void main(String[] args)
    {
        CatFactory factory = new CatFactory();
        //创建波斯猫对象
        ICat persianCat = factory.GetCat("PersianCat");
        //创建狸花猫对象
        ICat foxFlowerCat = factory.GetCat("FoxFlowerCat");
        //创建挪威森林猫对象
        ICat norwegianForestCat = factory.GetCat("NorwegianForestCat");
    }
}

Шаблон проектирования Simple Factory — это шаблон создания, обеспечивающий оптимальный способ создания объектов. Это описание шаблона Factory в Design Patterns. Фабричный паттерн в какой-то степени решает проблему создания объектов, и проект больше не будет пропитан «запахом нового кода». Но есть проблема, которая не решена: какой объект создавать, определяется некоторыми условиями во время выполнения. При изменении или расширении необходимо открыть этот код (заводской код реализации) для модификации, что нарушает принцип «близости к модификации». Существует также очень тесная зависимость этого кода, а высокий уровень зависит от нижнего слоя (клиент зависит от реализации конкретного класса (фабричный класс)), потому что решение о том, какой объект создавать, реализуется на фабрике. класс. К счастью, у нас также есть принцип инверсии зависимостей и шаблон абстрактной фабрики, чтобы спасти нас.

Абстрактная фабрика и принцип инверсии зависимостей

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

Сначала нам нужно создать заводской интерфейс:

public interface IFactory
{
    ICat CreateCat();
    Duck CreateDuck();
}

Затем создайте фабричный класс, создающий сцену:

public class BeijingSceneFactory:IFactory
{
    public ICat CreateCat()
    {
        return new FoxFlowerCat();
    }

    public Duck CreateDuck()
    {
        return new BeijingDuck();
    }
}

public class ShanDongSceneFactory:IFactory
{
    public ICat CreateCat()
    {
        return new FoxFlowerCat();
    }

    public Duck CreateDuck()
    {
        return new ShanDongDuck();
    }
}

Создайте класс сцены (псевдокод):

public class Scene 
{
    private ICat cat;
    private Duck duck;

    IFactory factory;
    public Scene(IFactory factory)
    {
        this.factory = factory;
    }

    public void Create(string role)
    {
        if(role == "duck")
        {
            duck = factory.CreateDuck();
        }
        if(role == "cat")
        {
            cat = factory.CreateCat();
        }
    }
}

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

public class FactoryPatternDemo 
{
    public static void main(String[] args)
    {
        IFactory factory = new BeijingSceneFactory();
        Scene scene = new Scene(factory);
        scene.Create("duck");
    }
}

Текущий дизайн кода ближе к понятию «инъекция» (внедрение зависимостей бедняка), смотрите внимательноScene scene = new Scene(factory);, Фабричный объект, который создает сцену, представляет собой абстрактный тип интерфейса и динамически передается через конструктор.Это преобразование дает нам возможность использовать структуру внедрения зависимостей. Конечно, между абстрактными фабриками и внедрением зависимостей есть еще одна проблема, о которой стоит подумать. Это вопрос «как разделить конфигурацию и использование компонентов», и ответ уже ясен — внедрение зависимостей.

Понимание разделения конфигурации и использования компонентов

Если мы считаем компонент более абстрактным, мы можем поставить "компоненты"понято как"объект"(основной компонент), затем соответствующий "конфигурация компонента«быть понятым как»инициализация объекта".в настоящее время"Разделение конфигурации и использования компонентов"Это предложение легко понять, то есть создание и использование объектов разделены. Преимущество этого очевидно, создание объектов отложено до этапа развертывания (это предложение может быть непростым для понимания), то есть , создание объектов Все полагаются на нашу унифицированную конфигурацию, мы можем изменить конфигурацию для динамической замены объектов, которые мы не хотим использовать, на объекты, которые мы хотим использовать, без изменения кода, использующего эти объекты. нужно поставитьсборка объектов(конфиг) иБизнес-код(использовать) для разделения.

внедрение зависимости

Внедрение зависимостей (DI) — это очень простая концепция, которую очень легко реализовать. Тем не менее, эта простота противоречит важности шаблона. Когда что-то простое и важное, люди чрезмерно усложняют его, и то же самое касается внедрения зависимостей. Чтобы понять внедрение зависимостей, нам сначала нужно распаковать слово и интерпретировать его — зависимость и внедрение.

Что такое зависимость?

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

Используйте ориентированный граф для моделирования зависимостей: A зависит от B:

В зависит от А:

Интернет предоставляет множество услуг, и услуги зависят от Интернета:

Пакеты (включая сборки и пространства имен) являются и клиентами, и службами:

Класс клиента зависит от класса обслуживания:

Некоторые сервисы прячутся за интерфейсами:

В ориентированных графах существует особый вид циклов, называемыйсамоциркуляция:

Рекурсия на уровне метода — хороший пример замкнутого цикла.

Зависимости в программных системах

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

Рисунок 1. Связанные объекты в программной системе:

Что такое инверсия управления (IOC)?

Связующие отношения возникают не только между объектами, но и между модулями программных систем, а также между программными системами и аппаратными системами. Как уменьшить связь между системами, модулями и объектами — это одна из целей, которую всегда преследует программная инженерия. Чтобы решить проблему чрезмерной связи между объектами, эксперт по программному обеспечению Майкл Мэттсон предложил теорию IOC для достижения «развязки» между объектами. В настоящее время эта теория была успешно применена к проекту, и были получены различные продукты структуры МОК. Точка зрения, выдвигаемая теорией ИОК, примерно такова: реализовать развязку между объектами с зависимостями посредством «третьих лиц». Как показано ниже: Рисунок 2. Процесс развязки IOC:

Из-за введения «третьей стороны» в среднее положение, то есть контейнера IOC, четыре объекта A, B, C и D не имеют отношения сцепления, и все передачи между шестернями полагаются на «третью». сторона», и контроль над всеми объектами. Все права передаются «стороннему» IOC-контейнеру. Таким образом, IOC-контейнер стал ключевым ядром всей системы. Он действует как «клей», склеивая все объекты в системе вместе.Без этого «клея» объекты потеряют контакт друг с другом, поэтому некоторые люди сравнивают контейнеры IOC с «клеем». Итак, как будет выглядеть система, если мы удалим контейнер IOC? Рис. 3. Система со снятым IOC-контейнером:

Удалите систему контейнера IOC, четыре объекта A, B, C и D не имеют отношения сцепления и связи друг с другом.В этом случае, когда вы реализуете A, вам не нужно рассматривать B и C в все.И D, зависимости между объектами были сведены к минимуму.

Прежде чем программная система представит контейнер IOC, как показано на рисунке 1, объект A зависит от объекта B, поэтому, когда объект A инициализируется или выполняется до определенного момента, он должен активно создавать объект B или использовать уже созданный объект B. Независимо от того, создается или используется объект B, контроль находится в его собственных руках. После внедрения IOC-контейнера в программную систему эта ситуация полностью изменилась.Как показано на рисунке 3, из-за добавления IOC-контейнера прямая связь между объектом A и объектом B теряется.Поэтому, когда объект A выполняется до точки, где требуется объект B. В то время контейнер IOC будет активно создавать объект B и внедрять его в то место, где он нужен объекту A. Путем сравнения до и после нетрудно увидеть, что процесс получения объектом А зависимого объекта В изменился от активного поведения к пассивному поведению, а права управления поменялись местами. контроль".

Что такое внедрение зависимостей?

В 2004 году Мартин Фаулер обсуждал тот же вопрос. Поскольку IOC — это инверсия управления, то что же такое «в каких аспектах управления инвертировано?» После подробного анализа и аргументации он пришел к ответу: «Получите процесс управления». полагаться на объекты наоборот». После реверсирования управления процесс получения зависимых объектов меняется с самостоятельного управления на активную инъекцию IOC-контейнером. Итак, он дал «Инверсии управления» более подходящее название «Внедрение зависимостей (Dependency Injection)». Его ответ, по сути, дает способ достижения МОК: инъекция.Так называемая инъекция зависимости заключается в том, что контейнер IOC динамически внедряет определенную зависимость в объект во время выполнения.. Итак, теперь мы знаем, что инверсия управления (IOC) и внедрение зависимостей (DI) — это описания одного и того же с разных точек зрения. Это достигается разделением между объектами путем введения контейнеров IOC и внедрения зависимостей.

Использование контейнеров Inversion of Control (IOC)

Мы часто сталкиваемся с такой ситуацией во время разработки: класс в разработке делегирует определенные абстракции для выполнения действий, и эти делегированные абстракции реализуются другими классами, которые, в свою очередь, делегируют некоторые другие абстракции для выполнения определенных действий. В конечном счете, там, где заканчивается цепочка зависимостей, остаются небольшие простые классы, которым больше не нужны никакие зависимости. Мы уже знаем, как добиться внедрения зависимостей (внедрение зависимостей для бедняков), вручную создав экземпляры классов и передав их конструктору. Хотя этот метод может произвольно заменить реализацию зависимостей, построенный граф объекта экземпляра все еще является статическим, то есть он был определен во время компиляции. Инверсия управления позволяет нам отложить построение графа объекта до времени выполнения. Система инверсии контейнеров управления может ассоциировать интерфейс, используемый приложением, с его классом реализации и может разрешать все соответствующие зависимости при получении экземпляра. Код примера не создает экземпляр реализации вручную, а использует контейнер Unity Inversion of Control для установления отношения сопоставления между классом и интерфейсом:

public partial class App:Application
{
    private IUnityContainer container;
    private void OnApplicationStartUp()
    {
        container = new UnityContainer();

        container.RegisterType<ISettings,ApplicationSettings>();
        container.RegisterType<ITaskService,TaskService>();

        var taskService = container.Resolve<ITaskService>();
    }
}

1. Первым шагом в коде является инициализация экземпляра UnityContainer. 2. После создания контейнера Unity нам нужно сообщить контейнеру, чему соответствует конкретный класс реализации каждого интерфейса в жизненном цикле приложения контейнера. Когда Unity сталкивается с каким-либо интерфейсом, он знает, какую реализацию разрешить. Если мы не укажем соответствующий класс реализации для интерфейса, Unity напомнит нам, что интерфейс не может быть создан. 3. После завершения регистрации связи между интерфейсом и соответствующим классом реализации нам необходимо получить экземпляр класса TaskService. Метод Resolve контейнера Unity проверяет конструктор класса TaskService, а затем пытается создать экземпляр зависимостей, внедряемых конструктором. Это повторяется до тех пор, пока экземпляры всех зависимостей во всей цепочке зависимостей не будут полностью созданы, и метод Resolve успешно создаст экземпляр класса TaskService.

Режим работы контейнера Inversion of Control (IOC) - регистрация, разрешение, режим выпуска

Все контейнеры Inversion of Control соответствуют простому интерфейсу всего с тремя методами, и Unity не является исключением. Хотя каждая инверсия контейнера управления имеет разные реализации, все они соответствуют следующему общему интерфейсу:

public interface IContainer:IDisposable
{
    void Register<TInterface,TImplementation>()
        where TImplementation:TInterface;

    TImplementation Resolve<TInterface>();

    void Release();
}
  • Регистрация: приложение сначала вызовет этот метод. И этот метод будет вызываться несколько раз, чтобы зарегистрировать сопоставление между различными интерфейсами и их реализациями. Предложение Where здесь используется для обеспечения того, чтобы тип TImplementation реализовывал интерфейс TInterface, который он наследует.
  • Разрешение: этот метод вызывается, когда приложение запускается для получения экземпляра объекта.
  • Освобождение: в течение жизненного цикла приложения, когда экземпляры определенных классов больше не нужны, этот метод можно вызвать для освобождения занимаемых ими ресурсов. Это может произойти в конце приложения или в подходящее время, когда приложение работает. Все мы знаем, что можем настроить, следует ли включать одноэлементный режим, когда зарегистрирован используемый нами контейнер Unity. Обычно ресурс действителен только для одного запроса, и метод Release вызывается после каждого запроса. Но когда мы настроим одноэлементный режим, метод Release будет вызываться только при закрытии приложения.

Императивная и декларативная регистрация

До сих пор мы использовали императивную регистрацию: императивный вызов методов из объектов-контейнеров. Преимущества обязательной регистрации:

  • Относительно кратко и легко читается.

  • Стоимость проверки на наличие проблем во время компиляции очень мала (например, предотвращение ошибок ввода кода и т. д.). Недостатки императивной регистрации:

  • Процесс регистрации был определен во время компиляции, если вы хотите заменить реализацию, вы должны изменить исходный код, а затем перекомпилировать.

Если декларативная регистрация выполняется с помощью конфигурации XML, перекомпиляция не требуется. Файл конфигурации приложения:

<configuration>
    <configSections name="unity" 
    type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection">
    </configSections>
    <unity>
        <container>
            <register type="ISettings" mapTo="ApplicationSettings"/>
            <register type="ITaskService" mapTo="TaskService"/>
        </container>
    </unity>
</configuration>

Запись приложения:

public partial class App:Application
{
    private IUnityContainer container;
    private void OnApplicationStartUp()
    {
        var section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
        container = new UnityContainer().LoadConfiguration(section);

        var taskService = container.Resolve<ITaskService>();
    }
}

Преимущества декларативной регистрации:

  • Отложите сопоставление интерфейсов и соответствующих реализаций до времени настройки. Недостатки декларативной регистрации:
  • Слишком громоздко и файл конфигурации будет огромным.
  • Ошибки при регистрации пропускают компиляцию и не обнаруживаются и не перехватываются до времени выполнения.

Три метода внедрения зависимостей, их преимущества и недостатки

Прежде всего, подумайте, почему вам необходимо использовать внедрение свойств на уровне контроллера и внедрение конструктора на уровне бизнес-логики в проекте?

1. Внедрение конструктора

public class TaskService
{
    private ITaskOneRepository taskOneRepository;
    private ITaskTwoRepository taskTwoRepository;
    public TaskService(
        ITaskOneRepository taskOneRepository,
        ITaskTwoRepository taskTwoRepository)
        {
            this.taskOneRepository = taskOneRepository;
            this.taskTwoRepository = taskTwoRepository;
        }
}

преимущество:

  • Зависимость от других классов отражается в конструкторе, и сразу видно, что этот класс нуждается в этих других классах для работы.
  • Из структуры IOC этот класс все еще работает (плохая надежность).
  • После успешной инициализации объекта его состояние должно быть правильным.

недостаток:

  • Конструктор будет иметь много параметров.
  • Некоторым классам требуется конструктор по умолчанию, например классу Controller платформы MVC.После внедрения конструктора использовать конструктор по умолчанию нельзя.

2. Внедрение свойств

public class TaskService
{
    private ITaskRepository taskRepository;
    private ISettings settings;
    public TaskService(
        ITaskRepository taskRepository,
        ISettings settings)
        {
            this.taskRepository = taskRepository;
            this.settings = settings;
        }
    public void OnLoad()
    {
        taskRepository.settings = settings;
    }
}

преимущество:

  • Зависимости могут быть динамически изменены в любой момент в течение всего времени жизни объекта.
  • Очень гибкий.

недостаток:

  • После создания объекта состояние времени до установки зависимого объекта неверно (экземпляр зависимости, внедренный из конструктора, может использоваться на протяжении всего жизненного цикла класса, также может использоваться экземпляр зависимости, внедренный из свойства из жизненного цикла класса в игру вступает некий промежуточный момент).
  • Не интуитивно ясно выражать, какие свойства требуются.

3. Метод инъекции

public class TaskRepository
{
    private ISettings settings;

    public void PrePare(ISettings settings)
    {
        this.settings = settings;
    }
}

преимущество:

  • более гибкий.

недостаток:

  • При добавлении новой зависимости исходная сигнатура метода будет уничтожена, и если этот метод уже используется многими другими модулями, это будет очень проблематично.
  • Как и в случае внедрения конструктора, будет много параметров.

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

Составление корней и разбор корней

1. Корень композиции В приложении должно быть только одно место вплоть до деталей внедрения зависимостей, и это место является корнем композиции. Здесь мы вручную создаем классы при использовании внедрения зависимостей бедняков, и здесь мы регистрируем интерфейсы и реализуем сопоставления между классами при использовании инверсии контейнера управления. Корень композиции предоставляет известное место для поиска конфигурации внедрения зависимостей, что может помочь вам избежать распространения зависимостей контейнера на другие части вашего приложения. 2. Разбор корней Концепция, тесно связанная с композиционными корнями, — это разбор корней. Это целевой граф объекта для анализакорневой узелтип объекта. Это очень абстрактно, например: корнем разрешения приложения MVC является контроллер. Запросы от браузера перенаправляются в методы контроллера, называемые действиями. Всякий раз, когда приходит запрос, среда MVC сопоставляет URL-адрес с именем контроллера, находит класс с соответствующим именем, создает его экземпляр и, наконец, запускает действие над этим экземпляром. Точнее, процесс инстанцирования контроллера — это процесс его разбора. Это означает, что мы можем легко следовать шаблону регистрации, разрешения и освобождения, чтобы свести к минимуму вызовы метода Resolve, который в идеале должен вызываться только в одном месте.

Корень композиции и корень синтаксического анализа являются еще одним вариантом «разделения конфигурации и использования компонентов», упомянутого выше.

Технический момент внедрения зависимостей

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

Суммировать

Подводя итог, мы начинаем с запаха нового кода и представляем два способа устранения запаха нового кода (развязки кода) — кодирование для интерфейса и использование внедрения зависимостей. Затем мы узнали о процессе от интерфейсно-ориентированного программирования до внедрения зависимостей, разработав небольшую игру. Наконец, я подробно представил большого Босса - инверсию управления (внедрение зависимостей), в основном представил, что такое зависимость, концепцию инверсии управления (внедрение зависимостей), использование контейнера инверсии управления (IOC), режим работы, императив. регистрация типа объявления, три метода внедрения зависимостей, их преимущества и недостатки, корень комбинации и корень разрешения, технические моменты внедрения зависимостей. Этот обмен стремится проанализировать внедрение зависимостей с принципиального и идеологического уровня. Поскольку мой уровень ограничен, некоторые моменты могут быть немного односторонними или недостаточно глубокими, поэтому я приведу справочные материалы, которыми собираюсь поделиться на этот раз. Студенты, заинтересованные в углубленном изучении, могут самостоятельно ознакомиться с этими материалами: 1. Практика гибкой разработки C#. Глава 2. Зависимости и уровни. Глава 9. Принципы внедрения зависимостей.

2. Шаблон проектирования HeadFirst Duck and Cat War адаптирован из главы 1 «Начало работы с шаблонами проектирования».


                                                     -----END-----


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