Приведи пример решения архитектурной проблемы

«Приведи пример решения архитектурной проблемы» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Проблема: Жесткая связанность (tight coupling) между высокоуровневым бизнес-сервисом и низкоуровневой реализацией (например, отправкой email). Это усложняет тестирование и замену компонентов.

Решение: Внедрение зависимости (Dependency Injection) через абстракции (интерфейсы).

// 1. Абстракция (контракт)
public interface INotificationService
{
    Task SendAsync(string to, string subject, string body);
}

// 2. Конкретная реализация
public class SmtpEmailService : INotificationService
{
    public async Task SendAsync(string to, string subject, string body)
    {
        // Логика отправки через SMTP (например, используя MailKit)
        Console.WriteLine($"Отправка email на {to}: {subject}");
        await Task.Delay(100); // Имитация асинхронной работы
    }
}

// 3. Другая реализация (для тестов или другой инфраструктуры)
public class MockNotificationService : INotificationService
{
    public List<string> SentLogs { get; } = new();
    public Task SendAsync(string to, string subject, string body)
    {
        SentLogs.Add($"{to}:{subject}");
        return Task.CompletedTask;
    }
}

// 4. Высокоуровневый сервис, зависящий от абстракции
public class OrderProcessor
{
    private readonly INotificationService _notifier;

    // Зависимость внедряется через конструктор
    public OrderProcessor(INotificationService notifier)
    {
        _notifier = notifier;
    }

    public async Task ProcessOrder(Order order)
    {
        // ... логика обработки заказа ...
        await _notifier.SendAsync(order.CustomerEmail, "Заказ подтвержден", "Ваш заказ принят в работу.");
    }
}

// 5. Компоновка (обычно в корне приложения или в DI-контейнере)
class Program
{
    static async Task Main()
    {
        // Выбор конкретной реализации происходит в одном месте
        INotificationService notificationService = new SmtpEmailService();
        // INotificationService notificationService = new MockNotificationService(); // Для тестов

        var processor = new OrderProcessor(notificationService);
        await processor.ProcessOrder(new Order());
    }
}

Выгоды:

  • Тестируемость: OrderProcessor легко протестировать, подсунув MockNotificationService.
  • Гибкость: Замена SMTP-провайдера на другой (например, SendGrid) требует лишь создания новой реализации INotificationService.
  • Соблюдение DIP (Принцип инверсии зависимостей): Модули верхнего уровня не зависят от модулей нижнего уровня. Оба зависят от абстракций.