Когда нужно вводить абстракцию (интерфейс или абстрактный класс)?

«Когда нужно вводить абстракцию (интерфейс или абстрактный класс)?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

Конкретные случаи из практики:

  1. Работа с внешними зависимостями: Все взаимодействия с базами данных, API, файловыми системами или сторонними сервисами (платежи, email, смс) я абстрагирую за интерфейсом. Это позволяет легко подменять реальную реализацию моком в unit-тестах.
  2. Наличие или ожидание нескольких реализаций: Если есть алгоритм сортировки, который может выполняться по-разному (QuickSort, MergeSort), или способ отправки уведомлений (Email, SMS, Push) — это прямой сигнал к введению абстракции.
  3. Следование принципу инверсии зависимостей (DIP): Модули верхнего уровня (бизнес-логика) не должны зависеть от модулей нижнего уровня (инфраструктура). Оба должны зависеть от абстракций.

Пример на C#:

// Абстракция для службы отправки уведомлений
public interface INotificationService
{
    Task SendAsync(string recipient, string message);
}

// Конкретная реализация для Email
public class EmailNotificationService : INotificationService
{
    public async Task SendAsync(string recipient, string message)
    {
        // Логика отправки email через SMTP или SendGrid API
        await _smtpClient.SendMailAsync(...);
    }
}

// Класс бизнес-логики, зависящий от абстракции
public class OrderProcessor
{
    private readonly INotificationService _notifier;
    // Внедрение зависимости через конструктор
    public OrderProcessor(INotificationService notifier)
    {
        _notifier = notifier;
    }

    public async Task ProcessOrder(Order order)
    {
        // ... логика обработки заказа
        await _notifier.SendAsync(order.CustomerEmail, "Your order is confirmed!");
    }
}

// В тестах используем заглушку
public class MockNotificationService : INotificationService
{
    public List<string> SentMessages { get; } = new();
    public Task SendAsync(string recipient, string message)
    {
        SentMessages.Add($"To: {recipient}, Msg: {message}");
        return Task.CompletedTask;
    }
}

Важное правило: Не создаю абстракцию «на будущее», если нет хотя бы одной конкретной причины или уже существующей второй реализации. Преждевременная абстракция так же вредна, как и ее отсутствие.