Основы Dependency Injection
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy. Урок 46 из 70.
Dependency Injection (DI) — это метод, при котором class получает свои dependency извне, а не создаёт их самостоятельно. Это делает код более гибким, пригодным для тестирования и менее coupled.
Без DI класс создаёт собственные зависимости, из-за чего его трудно изменять или тестировать:
public class OrderService
{
private EmailSender sender = new EmailSender(); // Жёстко связанный
public void PlaceOrder() => sender.Send("Order placed");
}При использовании DI зависимости передаются через конструктор:
public interface IMessageSender
{
void Send(string message);
}
public class EmailSender : IMessageSender
{
public void Send(string message) => Console.WriteLine("Email: " + message);
}
public class OrderService
{
private IMessageSender sender;
public OrderService(IMessageSender sender)
{
this.sender = sender;
}
public void PlaceOrder() => sender.Send("Order placed");
}Теперь OrderService не знает и не заботится о том, использует ли он Email, SMS или mock-отправитель для тестирования. Зависимость «injected» при создании объекта:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Заказ размещёнЭтот паттерн следует принципу зависимости от абстракций (интерфейсах), а не от конкретных реализаций. Он позволяет заменять реализации без изменения class, который их использует, что крайне важно для модульного тестирования и поддержания гибких архитектур.
Задание
ЛегкоДавайте создадим систему Logging, демонстрирующую возможности dependency injection. Вместо того чтобы жёстко задавать способ записи Log, вы создадите гибкий класс Logger, который может работать с любым местом назначения Log — консолью, имитацией file или чем-либо ещё — не зная конкретных деталей.
Вы организуете свой код в трёх files:
ILogWriter.cs: Define interface с именемILogWriterв namespaceLogging. Этот interface представляет контракт для любого места назначения Log и должен иметь один methodWrite(string message), возвращающий string с описанием того, куда было записано сообщение.LogWriters.cs: Create две конкретные реализацииILogWriterв namespaceLogging:ConsoleLogWriter— его methodWriteвозвращаетConsole: {message}FileLogWriter— принимает filename через свой constructor, а его methodWriteвозвращаетFile[{filename}]: {message}
Logger.cs: Create классLoggerв namespaceLogging, который получает свою dependency через constructor injection. КлассLoggerдолжен:- Принимать
ILogWriterчерез свой constructor и сохранять его в private field - Иметь method
Log(string message), который delegates вызов injected writer и возвращает результат
- Принимать
Program.cs: Продемонстрировать dependency injection, создав два разных Logger — один сConsoleLogWriter, а другой сFileLogWriter. Показать, как один и тот же классLoggerведёт себя по-разному в зависимости от того, какая dependency injected.
Вы получите два inputs:
- filename для file logger
- message для записи в Log
Create Logger с ConsoleLogWriter и запишите message в Log. Затем create another Logger с FileLogWriter (используя filename из input) и запишите в Log то же message. Выведите каждый результат на отдельной строке.
Например, если inputs — app.log и System started, output должен быть таким:
Console: System started
File[app.log]: System startedОбратите внимание, что класс Logger никогда не изменяется — он работает с любой реализацией ILogWriter. В этом и заключается суть dependency injection: Logger зависит от абстракции (interface), а конкретная реализация "injected" извне. Позже можно легко добавить DatabaseLogWriter или CloudLogWriter, вообще не изменяя класс Logger!
Попробуйте сами
using System;
using Logging;
class Program
{
public static void Main(string[] args)
{
// Прочитать входные данные
string filename = Console.ReadLine();
string message = Console.ReadLine();
// TODO: Создать Logger с ConsoleLogWriter и залогировать сообщение
// Вывести результат
// TODO: Создать ещё один Logger с FileLogWriter (используя filename) и залогировать то же сообщение
// Вывести результат
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методовПотренируйтесь самостоятельно: Онлайн-компилятор C#