Продвинутое ООП: Наследование, интерфейсы и полиморфизм
Студент поймет принципы наследования и инкапсуляции, научится применять интерфейсы на практике при проектировании различных платежных шлюзов.
Наследование и абстракция
Продолжаем углубляться в ООП. Наследование позволяет одному классу (дочернему) унаследовать свойства и методы другого класса (родительского). Для этого используется ключевое слово extends. Это избавляет от дублирования кода. Например, у нас может быть базовый класс User, от которого наследуются Customer и Admin. Однако в современной разработке (и в Laravel в частности) чаще применяется композиция и программирование на уровне интерфейсов. Интерфейсы решают проблему жесткой связанности кода. Интерфейс — это контракт. Он не содержит реализации (тела функций), он лишь описывает, какие методы должен обязательно реализовать класс, который этот интерфейс применяет (имплементирует).
В контексте нашего интернет-магазина (см. архив) нам нужно интегрировать две платежные системы: Stripe и Vipps. Если мы жестко привяжем логику Checkout-контроллера к классу Stripe, переключиться на Vipps будет крайне сложно. Вместо этого мы создаем интерфейс PaymentGateway. Этот интерфейс гарантирует, что любой платежный шлюз в нашем проекте будет иметь метод pay(float $amount): bool. Это проявление полиморфизма — мы можем работать с разными объектами (Stripe, Vipps) через единый интерфейс, не задумываясь о том, как именно они реализованы внутри.
<?php
// Архивные материалы: Работа с интерфейсами
interface PaymentGateway {
public function pay(float $amount): bool;
}
// Класс Stripe обязуется реализовать метод pay
class StripeGateway implements PaymentGateway {
public function pay(float $amount): bool {
echo "Обработка платежа Stripe на сумму $amount\n";
// Обращение к Stripe API...
return true;
}
}
// Класс Vipps также реализует этот интерфейс
class VippsGateway implements PaymentGateway {
public function pay(float $amount): bool {
echo "Инициация платежа Vipps на сумму $amount\n";
// Обращение к Vipps API...
return true;
}
}
// Функция принимает ЛЮБОЙ класс, реализующий интерфейс
function processOrder(PaymentGateway $gateway, float $total) {
if ($gateway->pay($total)) {
echo "Заказ успешно оплачен!";
}
}
$stripe = new StripeGateway();
processOrder($stripe, 150.00);
Внедрение зависимостей (Dependency Injection)
Пример кода выше демонстрирует фундаментальный паттерн проектирования — Внедрение зависимостей (DI). Функция processOrder не создает объект StripeGateway внутри себя с помощью new. Вместо этого она требует передать ей готовый объект, который соответствует контракту PaymentGateway (это указано в типизации аргумента). Почему это важно? Потому что фреймворк Laravel, который мы начнем изучать позже, построен вокруг мощного Service Container, который автоматически разрешает (injects) такие зависимости.
Типичная ошибка новичков — создание экземпляров классов внутри контроллеров. Это делает код нетестируемым. Если вы используете интерфейсы и DI, вы легко можете создать MockGateway (заглушку) для автоматического тестирования корзины без реального списания средств. Принцип "Программируйте на уровне интерфейсов, а не реализаций" (SOLID принципы) — это ключ к масштабируемой архитектуре e-commerce приложений.
Флеш-карточки
Какое ключевое слово используется для реализации интерфейса классом?
Нажмите, чтобы увидеть ответ
implements
Нажмите, чтобы вернуться
Какое ключевое слово используется для наследования одного класса от другого?
Нажмите, чтобы увидеть ответ
extends
Нажмите, чтобы вернуться
Может ли интерфейс в PHP содержать реализацию метода (тело функции)?
Как называется принцип ООП, позволяющий функциям обрабатывать объекты разных классов через единый интерфейс?
Задание
Scaffolding: Добавление нового шлюза
- Создайте класс PayPalGateway.
- Укажите, что он имплементирует интерфейс PaymentGateway.
- Реализуйте метод pay(float $amount), возвращающий true.
- Измените вызов processOrder(), передав в него экземпляр PayPalGateway.