Продвинутое ООП и интерфейсы
Принципы наследования, полиморфизма и работа с интерфейсами для подготовки к интеграции платежных шлюзов (Stripe, Vipps).
Наследование и Интерфейсы: Контрактное программирование
В этом уроке мы применим Project-Based Learning (Проектное обучение). Наша цель в реальном проекте — интегрировать оплату. Но мы хотим поддерживать несколько платежных систем: Stripe (для международных карт) и Vipps (популярно в Норвегии). Если мы жестко привяжем наш код к Stripe, добавление Vipps потребует переписывания половины приложения. Чтобы этого избежать, мы используем Интерфейсы и Полиморфизм.
Интерфейс в ООП — это строгий контракт. Он описывает, что должен делать класс (какие методы он должен иметь), но не описывает, как он должен это делать. Любой класс, который реализует (implements) интерфейс, обязан содержать код для всех методов, описанных в интерфейсе.
Полиморфизм означает 'множество форм'. В контексте программирования это способность функции обрабатывать объекты разных классов одинаково, если они реализуют один и тот же интерфейс. Наш класс оформления заказа (Checkout) не должен знать, как работает Stripe или Vipps под капотом. Он должен знать только одно: у любого переданного ему платежного шлюза есть метод pay().
Это критически важный принцип архитектуры (SOLID - Dependency Inversion Principle), который позволяет масштабировать интернет-магазин без боли.
<?php
// Практика: Работа с интерфейсами (Архивный вариант 1, Упражнение 1.3)
// Создаем контракт (интерфейс)
interface PaymentGateway {
// Любой класс, реализующий этот интерфейс, обязан иметь метод pay
// принимающий float и возвращающий bool
public function pay(float $amount): bool;
}
// Реализация для Stripe
class StripeGateway implements PaymentGateway {
public function pay(float $amount): bool {
// Здесь была бы реальная логика обращения к API Stripe
echo "Обработка платежа ${amount} через Stripe... Успешно!\n";
return true;
}
}
// Реализация для Vipps
class VippsGateway implements PaymentGateway {
public function pay(float $amount): bool {
// Здесь была бы логика инициации платежа через Vipps API
echo "Инициация платежа ${amount} через Vipps... Ожидание подтверждения.\n";
return true;
}
}
// Клиентский код (например, метод в CheckoutController)
// Мы требуем ЛЮБОЙ объект, который реализует интерфейс PaymentGateway
function processOrder(PaymentGateway $gateway, float $total) {
if ($gateway->pay($total)) {
echo "Заказ успешно оформлен!\n";
} else {
echo "Ошибка оплаты.\n";
}
}
// Теперь мы можем легко менять платежные шлюзы, не меняя логику processOrder!
$stripe = new StripeGateway();
processOrder($stripe, 150.00);
$vipps = new VippsGateway();
processOrder($vipps, 45.50);
?>
Типичные ошибки новичков при работе с интерфейсами
При использовании метода Mastery Learning важно разобрать частые ошибки, чтобы не допускать их в будущем:
- Изменение сигнатуры метода: Если в интерфейсе метод определен как
pay(float $amount): bool, вы не можете в классеStripeGatewayнаписатьpay($amount)без указания типов, или изменить тип аргумента наint. Сигнатура должна совпадать абсолютно точно, иначе PHP выдаст фатальную ошибку (Fatal error: Declaration must be compatible). - Добавление логики в интерфейс: Интерфейсы не могут содержать реализацию методов (тело функции с фигурными скобками
{}). Они содержат только объявления. - Создание экземпляра интерфейса: Вы не можете написать
new PaymentGateway(). Интерфейс — это абстракция, создать можно только объект конкретного класса, который этот интерфейс реализует.
Использование интерфейсов подготавливает нас к паттерну Service Container в Laravel, где мы сможем автоматически подменять реализации (например, использовать фиктивный шлюз для тестов и реальный Stripe для production).