Принципы 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 эти принципы можно легко применять, используя такие возможности фреймворка, как сервис-контейнер, внедрение зависимостей и интерфейсы.