Menu
Coddy logo textTech

Основы 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, который их использует, что крайне важно для модульного тестирования и поддержания гибких архитектур.

challenge icon

Задание

Легко

Давайте создадим систему Logging, демонстрирующую возможности dependency injection. Вместо того чтобы жёстко задавать способ записи Log, вы создадите гибкий класс Logger, который может работать с любым местом назначения Log — консолью, имитацией file или чем-либо ещё — не зная конкретных деталей.

Вы организуете свой код в трёх files:

  • ILogWriter.cs: Define interface с именем ILogWriter в namespace Logging. Этот interface представляет контракт для любого места назначения Log и должен иметь один method Write(string message), возвращающий string с описанием того, куда было записано сообщение.
  • LogWriters.cs: Create две конкретные реализации ILogWriter в namespace Logging:
    • ConsoleLogWriter — его method Write возвращает Console: {message}
    • FileLogWriter — принимает filename через свой constructor, а его method Write возвращает File[{filename}]: {message}
  • Logger.cs: Create класс Logger в namespace Logging, который получает свою 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) и залогировать то же сообщение
        // Вывести результат
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C#