Внедрение зависимостей. IoC-контейнеры Лекция 03.

Презентация:



Advertisements
Похожие презентации
Учебный курс Объектно-ориентированный анализ и программирование Лекция 7 Методы как средство реализации операций Лекции читает кандидат технических наук.
Advertisements

Методики «Inversion of Control» и «Dependency Injection». Применение в Spring. Малышкин Фёдор
С# и ООП Формальное определение класса с C# Класс в C# - это пользовательский тип данных (user defined type), который состоит из данных (часто называемых.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Классы в С#. Перечисления С# Перечисление задает конечное множество возможных значений, которые могут получать объекты класса перечисление. [атрибуты][модификаторы]
ORM Паттерны. Repository Repository (хранилище) выступает в роли посредника между слоем домена и слоем отображения данных, предоставляя интерфейс в виде.
Обзор возможностей Инверсия управления Аспектно-ориентированное программирование.
Наследование и полиморфизм. «Быть» или «Иметь» а так же «Точно» или «Как получится»
Объектно-ориентированный подход в языке C#. Класс в языке C# - ссылочный тип, определенный пользователем. Для классов ЯП C# допустимо только единичное.
Наследование Полиморфизм ВЫЗОВ КОНСТРУКТОРОВ И ДЕСТРУКТОРОВ ПРИ НАСЛЕДОВАНИИ.
Лекция 2: Описание класса 1. Поля 2. Методы 3. Конструкторы.
Дружественные функции Дружественные функции – это функции, объявленные вне класса, но имеющие доступ к закрытым и защищенным полям данного класса Дружественная.
Основы ООП и C# Работа с объектами и классами. Классы Класс специальный тип данных для описания объектов. Он определяет данные и поведение типа. Определение.
В С# предусмотрены средства для создания пользовательских классов-контейнеров, к внутренним элементам которых можно обращаться при помощи того же оператора.
Классы в C#. Две роли классов Класс Класс – это модуль, архитектурная единица построения программной системы. Модульность построения – основное свойство.
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Высокоуровневые методы информатики и программирования Лекция 14 Интерфейсы.
ИТЕРАТОРЫ И LINQ Лекция 1. Интерфейс IEnumerable и IEnumerator Любая коллекция реализует интерфейс IEnumerable. public interface IEnumerable : IEnumerable.
Обзор возможностей Инверсия управления Аспектно-ориентированное программирование.
1 © Luxoft Training 2012 Java: расширенные вопросы Модуль #8.
Транксрипт:

Внедрение зависимостей. IoC-контейнеры Лекция 03

Предпосылки Вернмся к примеру с обращением зависимостей. В результате применения указанного принципа мы получили следующий код: public class DataScripter { private readonly IDataDumper dataDumper; private readonly TextWriter writer; public DataScripter(IDataDumper dataDumper, TextWriter writer) { this.dataDumper = dataDumper; this.writer = writer; } public void GetSqlDump(IDataReader dataReader) { while (dataReader.Read()) { string rowSqlDump = dataDumper.GetRowDump(dataReader); writer.WriteLine(rowSqlDump); }}}}}}

Предпосылки Пример использования вышеприведенного кода: public static void Main(string[] args) { IDbConnection connection = CreateConnection(); DataScripter scripter = new DataScripter(new InsertDataDumper(), Console.Out); using (IDbCommand command = connection.CreateCommand()) { command.CommandText = string.Format("SELECT * FROM {0}", args[0]); using (IDataReader reader = command.ExecuteReader()) { scripter.GetSqlDump(reader); }}}}}}

Пассивная инверсия зависимостей Заметим, что в рассмотренном выше случае мы применяли принцип инверсии зависимостей, а после этого внедряли зависимые объекты, передавая их параметрами конструктора. Существуют и другие способы передать конкретные реализации классов некоторому объекту, такие как: Property injection Interface injection Method Injection Указанные выше способы составляют группу пассивной инверсии зависимостей необходимые объекты «впрыскиваются» в host-объект.

Пассивная инверсия зависимостей Method Injection: public class DataScripter { private readonly TextWriter writer; public IDataDumper DataDumper { get; set; } public DataScripter(TextWriter writer) { this.writer = writer; }}}} public class DataScripter { private readonly IDataDumper dataDumper; private TextWriter writer; public void ConfigureTextWriter(TextWriter writer) { this.writer = writer; } public DataScripter(IDataDumper dataDumper) { this.dataDumper = dataDumper; }}}} Property injection:

Пассивная инверсия зависимостей Interface injection подразумевает введение дополнительного интерфейса, который позволяет внедрять зависимости определенного типа: public interface ITextWriterInjector { void InjectTextWriter(TextWriter writer); } public class DataScripter : ITextWriterInjector{ private readonly IDataDumper dataDumper; private TextWriter writer; public void InjectTextWriter(TextWriter writer) { this.writer = writer; } public DataScripter(IDataDumper dataDumper) { this.dataDumper = dataDumper; }}}}

Предпосылки использования IoC-контейнеров Обратим внимание на тот факт, что конкретный экземпляр объекта, который следует передать в конструктор, указывается непосредственно в точке вызова конструктора. Существует несколько способов инкапсуляции выбора конкретной реализации интерфейса: использование фабричных методов и абстрактных фабрик; использование паттерна ServiceLocator и его разновидностей.

Паттерн Абстрактная фабрика Абстрактная фабрика может иметь следующий вид: public interface IDataScripterFactory { DataScripter CreateDataScripter(); } public class DataScripterFactory : IDataScripterFactory { protected virtual IDataDumper CreateDataDumper() { return new InsertDataDumper(); } protected virtual TextWriter GetOutputTextWriter() { return Console.Out; } public DataScripter CreateDataScripter() { return new DataScripter(CreateDataDumper(), GetOutputTextWriter()); }}}}

Паттерн Абстрактная фабрика Также она может принимать экземпляры IDataDumper и TextWriter параметрами конструктора: public class DataScripterFactory : IDataScripterFactory { private readonly IDataDumper dataDumper; private readonly TextWriter outputWriter; public DataScripterFactory(IDataDumper dataDumper, TextWriter outputWriter) { this.dataDumper = dataDumper; this.outputWriter = outputWriter; } public DataScripter CreateDataScripter() { return new DataScripter(dataDumper, outputWriter); }}}}

Паттерн Абстрактная фабрика Также возможен следующий вариант параметризации: public class DataScripterFactory : IDataScripterFactory where TDataDumper : IDataDumper, new() { public DataScripter CreateDataScripter() { return new DataScripter( new TDataDumper(), Console.Out); }}}}

Паттерн ServiceLocator Суть паттерна ServiceLocator заключена в следующем классе: Он позволяет инкапсулировать связь между интерфейсом и е конкретной реализацией. public static class ServiceLocator { private static readonly Dictionary services = new Dictionary (); public static void RegisterService (Type service) { services[typeof (T)] = service; } public static T Resolve () { return (T) Activator.CreateInstance(services[typeof (T)]); }}}}

Паттерн ServiceLocator Метод Resolve. Позволяет получить конкретную реализацию абстрактного класса или интерфейса, переданного параметром шаблона. Метод RegisterService. Регистрирует конкретную реализацию абстрактного класса или интерфейса. Поле Dictionary services позволяет классу ServiceLocator поддерживать отображение одного типа данных на другой.

Паттерн ServiceLocator Теперь мы можем переписать класс DataScripter следующим образом: public class DataScripter { private readonly IDataDumper dataDumper; private readonly TextWriter writer; public DataScripter(TextWriter writer) : this(ServiceLocator.Resolve (), writer) { } public DataScripter(IDataDumper dataDumper, TextWriter writer) { this.dataDumper = dataDumper; this.writer = writer; }}}}

Паттерн ServiceLocator Теперь использование класса DataScripter выглядит так: public static void Main(string[] args) { ServiceLocator.RegisterService (typeof(InsertDataDumper)); DataScripter scripter = new DataScripter(Console.Out); IDbConnection connection = CreateConnection(); using (IDbCommand command = connection.CreateCommand()) { command.CommandText = string.Format("SELECT * FROM {0}", args[0]); using (IDataReader reader = command.ExecuteReader()) { scripter.GetSqlDump(reader); }}}}}}

Активная инверсия зависимостей Заметим, что конструктор объекта DataScripter знает про глобальный объект ServiceLocator и запрашивает у него все необходимые интерфейсы: Если зависимый класс сам обращается к классу ServiceLocator, то такой подход называется pull approach и входит в группу активной инверсии зависимостей. public class DataScripter { private readonly IDataDumper dataDumper; private readonly TextWriter writer; public DataScripter(TextWriter writer) : this(ServiceLocator.Resolve (), writer) { } }

Активная инверсия зависимостей Другой вариант активной инверсии зависимостей состоит в том, что объект локатора передатся извне. Такой подход называется push approach. Данный способ инверсии зависимостей демонстрирует следующий вариант конструктора объекта DataScripter: public class DataScripter { private readonly IDataDumper dataDumper; private readonly TextWriter writer; public DataScripter(ServiceLocator serviceLocator, TextWriter writer) : this(serviceLocator.Resolve (), writer) { } }

Паттерн ServiceLocator Обратим внимание, что до обращения зависимостей объект DataScripter знал про конкретные классы и создавал их напрямую.

Паттерн ServiceLocator После обращения зависимостей объект DataScripter стал использовать только интерфейсы:

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

IoC-контейнеры Заметим, что класс ServiceLocator требует некоторой доработки. Например, мы можем захотеть контролировать количество создаваемых экземпляров класса. IoC-контейнер или Dependency Injection Framework это набор классов, которые реализуют механизм внедрения зависимостей..NET Ninject Unity Application Block PHP Symfony Dependency Injection DIContainer C++ Autumn Framework QtIOCContainer Java Модуль в составе Spring Framework Модуль в составе Seasar

IoC-контейнеры IoC-контейнеры позволяют инкапсулировать в отдельном классе выбор конкретных реализаций интерфейсов или абстрактных классов, а также выполнять автоматическое внедрение этих реализаций в зависимый объект. Механизм работы IoC-контейнеров демонстрирует следующий фрагмент псевдокода: class DataScipterIOCContainer : IOCContainer { protected override Configure() { Use(InsertDataDumper).AsImplementation(IDataDumper); Use(Console.Out).AsImplementation(TextWriter); }}}} public void Usage() { IOCContainer container = new DataScipterIOCContainer(); DataScripter scripter = container.Create(DataScripter); }

Ninject Рассмотрим использование IoC-контейнеров на примере Ninject. Классом, который инкапсулирует в себе информацию о выборе конкретной реализации интерфейса, является наследник NinjectModule. В следующем фрагменте кода объявляется, что в качестве реализаций интерфейсов IDataDumper и TextWriter следует использовать экземпляр класс InsertDataDumper и значение свойства Console.Out соответственно: public class ApplicatoinModule : NinjectModule { public override void Load() { Bind ().To (); Bind ().ToMethod(c => Console.Out); }}}}

Ninject Использование рассмотренного выше класса выглядит следующим образом: Где StandardKernel это класс библиотеки Ninject, который позволяет внедрять зависимости в соответствии с правилами, описанными в экземпляре объекта, передаваемого в конструктор. public static void NinjectUsage() { StandardKernel kernel = new StandardKernel(new ApplicatoinModule()); TextWriter writer = kernel.Get (); writer.WriteLine("..."); }

Ninject Рассмотрим теперь использование Ninject для класса DataScripter. Для этого объявим класс-наследник NinjectModule следующим образом: В классе DataScripter необходимо отметить конструктор атрибутом [Inject]: public class ScriptingModule : NinjectModule { public override void Load() { Bind ().To (); Bind ().ToMethod(c => Console.Out); Bind ().ToSelf(); }}}} public class DataScripter { [Inject] public DataScripter(IDataDumper dataDumper, TextWriter writer) }

Ninject Теперь клиентский код будет выглядеть следующим образом: public static void Main(string[] args) { StandardKernel kernel = new StandardKernel(new ScriptingModule()); DataScripter scripter = kernel.Get (); IDbConnection connection = CreateConnection(); using (IDbCommand command = connection.CreateCommand()) { command.CommandText = string.Format("SELECT * FROM {0}", args[0]); using (IDataReader reader = command.ExecuteReader()) { scripter.GetSqlDump(reader); }}}}}}

Ninject Помимо внедрения зависимостей через конструктор Ninject также поддерживает внедрение зависимостей через свойства и методы. Для внедрения зависимости через свойство его необходимо пометить атрибутом [Inject]: public class DataScripter { private readonly TextWriter writer; [Inject] public IDataDumper DataDumper { get; set; } [Inject] public DataScripter(TextWriter writer) { this.writer = writer; }}}}

Ninject Внедрение зависимости посредством метода происходит аналогичным образом: public class DataScripter { private readonly IDataDumper dataDumper; private TextWriter writer; [Inject] public void ConfigureTextWriter(TextWriter writer) { this.writer = writer; } [Inject] public DataScripter(IDataDumper dataDumper) { this.dataDumper = dataDumper; }}}}

DIContainer Рассмотрим теперь библиотеку внедрения зависимостей DIContainer для PHP5. Контейнер зависимостей в данном случае базируется на конфигурационных XML-файлах. Рассмотрим аналогичный класс DataScripterа на языке PHP: class DataScripter { private $dataDumper; private $writer; public function __construct(IDataDumper $dataDumper, ITextWriter $writer) { $this->dataDumper = $dataDumper; $this->writer = $writer; } public function GetSqlDump(IDataReader $dataReader) { while ($dataReader->Read()) { $rowSqlDump = $this->dataDumper->GetRowDump(dataReader); $this->writer->WriteLine($rowSqlDump); }}}}}}

DIContainer Для указания правил выбора конкретных реализаций используется XML-файл следующего вида: Клиентский код выглядит следующим образом: $container = S2ContainerFactory::create('scripter.dicon'); // scripter.dicon – вышеприведенный файл. $dataScripter = $container->getComponent('DataScripter'); //... $dataScripter->GetSqlDump($reader);

DIContainer Данная библиотека поддерживает внедрение зависимостей через метод установки (setter injection). Для этого метод установки должен начинаться с set. Конфигурационный файл и клиентский код остаются без изменений. class DataScripter { private $dataDumper; private $writer; public function __construct(ITextWriter $writer) { $this->writer = $writer; } public function setDataDumper(IDataDumper $dataDumper) { $this->dataDumper = $dataDumper; }}}}

Недостатки IoC-контейнеров IoC-контейнеры зачастую являются лишь механизмом для преодоления трудностей, вызванных «неправильным» дизайном приложений. Затруднено тестирования инварианта, определяемого в модуле привязки интерфейсов к конкретным реализациям.