Расскажи про свой опыт реализации паттернов с использованием абстракций

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

Ответ

Использование абстракций (интерфейсов и абстрактных классов) — это основа большинства паттернов проектирования в моей работе. Это позволяет создавать гибкий, расширяемый и тестируемый код.

1. Стратегия (Strategy) — самый частый случай: В проекте по обработке платежей нужно было поддерживать множество платежных шлюзов (Stripe, PayPal, банковские карты). Вместо гигантского класса с кучей if-else я использовал паттерн Стратегия.

interface PaymentGateway {
    public function charge(float $amount, array $customerData): PaymentResult;
    public function refund(string $transactionId): bool;
}

class StripeGateway implements PaymentGateway {
    public function charge(float $amount, array $customerData): PaymentResult {
        // Логика вызова API Stripe
        $charge = StripeCharge::create([...]);
        return new PaymentResult($charge->id, $charge->status);
    }
    // ... метод refund
}

class PayPalGateway implements PaymentGateway {
    // ... своя реализация
}

// Контекст, который использует абстракцию
class PaymentService {
    private PaymentGateway $gateway;

    public function __construct(PaymentGateway $gateway) {
        $this->gateway = $gateway; // Внедряем конкретную стратегию
    }

    public function processOrder(Order $order): void {
        $result = $this->gateway->charge($order->getTotal(), $order->getCustomerData());
        $order->markAsPaid($result->getTransactionId());
    }
}

// Использование. Выбор стратегии может происходить на основе конфига или данных заказа.
$gateway = new StripeGateway(); // Или PayPalGateway(), или другой
$paymentService = new PaymentService($gateway);
$paymentService->processOrder($order);

2. Фабричный метод (Factory Method) и Абстрактная фабрика: Использовал для создания семейств связанных объектов. Например, в системе отчетов были разные форматы вывода (PDF, Excel, HTML) и разные типы отчетов (финансовый, статистический). Абстрактная фабрика позволяла гарантировать, что для «Финансового отчета» будет создан корректный набор объектов (PDF-рендерер с финансовыми шаблонами).

3. Внедрение зависимостей (Dependency Injection): Это не столько паттерн, сколько практика, целиком построенная на абстракциях. Все мои сервисы в Symfony/Laravel проектах зависят от интерфейсов (репозиториев, клиентов внешних API, сервисов нотификации), а не от конкретных классов. Это позволяет:

  • Легко тестировать: В тестах я подменяю реальную реализацию на Mock-объект, реализующий тот же интерфейс.
  • Легко менять поведение: Чтобы перейти с одной облачной файловой системы (например, AWS S3) на другую (Google Cloud Storage), нужно лишь написать новую реализацию интерфейса FileStorageInterface и изменить конфигурацию DI-контейнера.

Итог: Работа с абстракциями через паттерны — это инвестиция в поддерживаемость кода. Первоначальная сложность окупается в разы при необходимости модификации, расширения или тестирования системы.