Как стать автором
Обновить

Принципы SOLID на примере Laravel

Уровень сложностиСредний

Принципы SOLID — это набор из пяти принципов объектно-ориентированного программирования и проектирования, которые помогают создавать более гибкие, поддерживаемые и расширяемые системы. Давайте рассмотрим каждый из этих принципов на примере кода на PHP с использованием фреймворка Laravel.

1. Принцип единственной ответственности (Single Responsibility Principle, SRP)

Определение: Класс должен иметь только одну причину для изменения, то есть он должен выполнять только одну задачу.

Пример:

class OrderController extends Controller
{
    public function store(Request $request)
    {
        // Валидация данных
        $validatedData = $request->validate([
            'product_id' => 'required|exists:products,id',
            'quantity' => 'required|integer|min:1',
        ]);

        // Создание заказа
        $order = $this->createOrder($validatedData);

        // Отправка уведомления
        $this->sendNotification($order);

        return response()->json($order, 201);
    }

    private function createOrder(array $data)
    {
        return Order::create([
            'product_id' => $data['product_id'],
            'quantity' => $data['quantity'],
        ]);
    }

    private function sendNotification(Order $order)
    {
        // Логика отправки уведомления
    }
}

Проблема: Класс OrderController выполняет несколько задач: валидацию, создание заказа и отправку уведомления.

Решение: Разделим ответственности на разные классы.

class OrderController extends Controller
{
    protected $orderService;

    public function __construct(OrderService $orderService)
    {
        $this->orderService = $orderService;
    }

    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'product_id' => 'required|exists:products,id',
            'quantity' => 'required|integer|min:1',
        ]);

        $order = $this->orderService->createOrder($validatedData);

        return response()->json($order, 201);
    }
}

class OrderService
{
    protected $notificationService;

    public function __construct(NotificationService $notificationService)
    {
        $this->notificationService = $notificationService;
    }

    public function createOrder(array $data)
    {
        $order = Order::create([
            'product_id' => $data['product_id'],
            'quantity' => $data['quantity'],
        ]);

        $this->notificationService->sendNotification($order);

        return $order;
    }
}

class NotificationService
{
    public function sendNotification(Order $order)
    {
        // Логика отправки уведомления
    }
}

Теперь каждый класс отвечает за одну задачу: OrderController — за обработку запроса, OrderService — за создание заказа, а NotificationService — за отправку уведомлений.

2. Принцип открытости/закрытости (Open/Closed Principle, OCP)

Определение: Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.

Пример:

class PaymentProcessor
{
    public function process($payment)
    {
        if ($payment->type === 'credit_card') {
            $this->processCreditCard($payment);
        } elseif ($payment->type === 'paypal') {
            $this->processPaypal($payment);
        }
    }

    private function processCreditCard($payment)
    {
        // Логика обработки кредитной карты
    }

    private function processPaypal($payment)
    {
        // Логика обработки PayPal
    }
}

Проблема: Если нужно добавить новый способ оплаты, придется изменять класс PaymentProcessor.

Решение: Используем стратегию с интерфейсами.

interface PaymentMethod
{
    public function process($payment);
}

class CreditCardPayment implements PaymentMethod
{
    public function process($payment)
    {
        // Логика обработки кредитной карты
    }
}

class PaypalPayment implements PaymentMethod
{
    public function process($payment)
    {
        // Логика обработки PayPal
    }
}

class PaymentProcessor
{
    public function process(PaymentMethod $paymentMethod, $payment)
    {
        $paymentMethod->process($payment);
    }
}

Теперь можно добавлять новые способы оплаты, не изменяя класс PaymentProcessor.

3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP)

Определение: Объекты в программе должны быть заменяемы экземплярами их подтипов без изменения правильности работы программы.

Пример:

class Bird
{
    public function fly()
    {
        // Логика полета
    }
}

class Ostrich extends Bird
{
    public function fly()
    {
        throw new Exception("Can't fly");
    }
}

Проблема: Класс Ostrich (страус) нарушает принцип LSP, так как он не может летать, хотя является подтипом Bird.

Решение: Пересмотрим иерархию классов.

class Bird
{
    // Общие методы для всех птиц
}

class FlyingBird extends Bird
{
    public function fly()
    {
        // Логика полета
    }
}

class Ostrich extends Bird
{
    // Логика для страуса
}

Теперь Ostrich не нарушает принцип LSP, так как он не обязан уметь летать.

4. Принцип разделения интерфейса (Interface Segregation Principle, ISP)

Определение: Клиенты не должны зависеть от интерфейсов, которые они не используют.

Пример:

interface Worker
{
    public function work();
    public function eat();
}

class HumanWorker implements Worker
{
    public function work()
    {
        // Логика работы
    }

    public function eat()
    {
        // Логика приема пищи
    }
}

class RobotWorker implements Worker
{
    public function work()
    {
        // Логика работы
    }

    public function eat()
    {
        // Робот не ест, но вынужден реализовывать метод
    }
}

Проблема: Класс RobotWorker вынужден реализовывать метод eat, который ему не нужен.

Решение: Разделим интерфейс на более мелкие.

interface Workable
{
    public function work();
}

interface Eatable
{
    public function eat();
}

class HumanWorker implements Workable, Eatable
{
    public function work()
    {
        // Логика работы
    }

    public function eat()
    {
        // Логика приема пищи
    }
}

class RobotWorker implements Workable
{
    public function work()
    {
        // Логика работы
    }
}

Теперь RobotWorker не зависит от интерфейса Eatable.

5. Принцип инверсии зависимостей (Dependency Inversion Principle, DIP)

Определение: Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа модулей должны зависеть от абстракций.

Пример:

class OrderService
{
    protected $database;

    public function __construct()
    {
        $this->database = new MySQLDatabase();
    }

    public function createOrder(array $data)
    {
        // Логика создания заказа
        $this->database->save($data);
    }
}

Проблема: Класс OrderService зависит от конкретной реализации базы данных MySQLDatabase.

Решение: Внедрим зависимость через интерфейс.

interface Database
{
    public function save(array $data);
}

class MySQLDatabase implements Database
{
    public function save(array $data)
    {
        // Логика сохранения в MySQL
    }
}

class OrderService
{
    protected $database;

    public function __construct(Database $database)
    {
        $this->database = $database;
    }

    public function createOrder(array $data)
    {
        // Логика создания заказа
        $this->database->save($data);
    }
}

Теперь OrderService зависит от абстракции Database, а не от конкретной реализации.

Заключение

Принципы SOLID помогают создавать более гибкие, поддерживаемые и расширяемые системы. В Laravel эти принципы можно легко применять, используя такие возможности фреймворка, как сервис-контейнер, внедрение зависимостей и интерфейсы.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.