Что такое принципы SOLID?
Sobes Copilot
Получайте ответы в реальном времени
Ответ от нейросети
sobes.tech AI
SOLID — это мнемоническая аббревиатура, обозначающая пять основных принципов проектирования объектно-ориентированного программного обеспечения, направленных на создание гибких, поддерживаемых и расширяемых систем:
- Single Responsibility Principle (Принцип Единой Ответственности)
- Open/Closed Principle (Принцип Открытости/Закрытости)
- Liskov Substitution Principle (Принцип Подстановки Лисков)
- Interface Segregation Principle (Принцип Разделения Интерфейсов)
- Dependency Inversion Principle (Принцип Инверсии Зависимостей)
Принцип Единой Ответственности (SRP): Класс должен иметь только одну причину для изменения. Это означает, что класс должен выполнять только одну конкретную задачу или иметь только одну ответственность.
<?php
// Плохо: класс Customer управляет данными клиента и отправкой Email
class BadCustomer {
public function saveData(array $data) { /* ... */ }
public function sendEmail(string $message) { /* ... */ }
}
// Хорошо: ответственность разделена
class CustomerData {
public function saveData(array $data) { /* ... */ }
}
class EmailService {
public function sendEmail(string $message) { /* ... */ }
}
?>
Принцип Открытости/Закрытости (OCP): Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации. Это достигается использованием абстракций (интерфейсов, абстрактных классов).
<?php
// Плохо: добавление нового типа фигуры требует изменения класса AreaCalculator
class BadAreaCalculator {
public function calculateArea(array $shapes): float {
$totalArea = 0;
foreach ($shapes as $shape) {
if ($shape instanceof Rectangle) {
$totalArea += $shape->getWidth() * $shape->getHeight();
} elseif ($shape instanceof Circle) {
$totalArea += pi() * $shape->getRadius() ** 2;
}
// Что если добавить Triangle? Придется менять этот класс
}
return $totalArea;
}
}
// Хорошо: Используем интерфейс для расширения без модификации
interface Shape {
public function calculateArea(): float;
}
class Rectangle implements Shape {
private float $width;
private float $height;
public function __construct(float $width, float $height) {
$this->width = $width;
$this->height = $height;
}
public function calculateArea(): float {
return $this->width * $this->height;
}
}
class Circle implements Shape {
private float $radius;
public function __construct(float $radius) {
$this->radius = $radius;
}
public function calculateArea(): float {
return pi() * $this->radius ** 2;
}
}
class GoodAreaCalculator {
public function calculateTotalArea(array $shapes): float {
$totalArea = 0;
foreach ($shapes as $shape) {
// Все фигуры, реализующие Shape, работают одинаково
$totalArea += $shape->calculateArea();
}
return $totalArea;
}
}
?>
Принцип Подстановки Лисков (LSP): Объекты в программе должны быть заменяемы экземплярами их подтипов без изменения правильности выполнения программы. Наследующие классы должны дополнять, а не заменять поведение базового класса.
<?php
// Плохо: Square не является полной заменой Rectangle, т.к. нарушает инвариант
class Rectangle {
protected float $width;
protected float $height;
public function setWidth(float $width): void { $this->width = $width; }
public function setHeight(float $height): void { $this->height = $height; }
public function calculateArea(): float { return $this->width * $this->height; }
}
class Square extends Rectangle {
public function setWidth(float $width): void {
$this->width = $width;
$this->height = $width; // Нарушает ожидание для Rectangle
}
public function setHeight(float $height): void {
$this->width = $height;
$this->height = $height; // Нарушает ожидание для Rectangle
}
}
function enforceRectangleArea(Rectangle $r): void {
$r->setWidth(5);
$r->setHeight(10);
// Ожидаем площадь 50, но для Square будет 100
echo "Area: " . $r->calculateArea() . "\n";
}
// Хорошо: использовать отдельные классы или пересмотреть иерархию
?>
Принцип Разделения Интерфейсов (ISP): Клиенты не должны быть вынуждены зависеть от интерфейсов, которые они не используют. Лучше иметь много маленьких, специфичных интерфейсов, чем один большой общий.
<?php
// Плохо: Один интерфейс для всех типов работников, включая тех, кто не ест
interface BadWorker {
public function work();
public function eat();
}
class HumanWorker implements BadWorker {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorker implements BadWorker {
public function work() { /* ... */ }
public function eat() { // Роботы не едят, но вынуждены реализовывать метод
throw new Exception("Robots don't eat!");
}
}
// Хорошо: Разделенные интерфейсы
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class HumanWorkerISP implements Workable, Eatable {
public function work() { /* ... */ }
public function eat() { /* ... */ }
}
class RobotWorkerISP implements Workable {
public function work() { /* ... */ }
// Не реализует Eatable
}
?>
Принцип Инверсии Зависимостей (DIP): Модули верхних уровней не должны зависеть от модулей нижних уровней. И те, и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
<?php
// Плохо: High-level класс Report напрямую зависит от Low-level класса DatabaseConnection
class DatabaseConnection {
public function connect() { /* ... */ }
public function getData() { /* ... */ return ['data']; }
}
class ReportGenerator {
private DatabaseConnection $dbConnection;
public function __construct() {
$this->dbConnection = new DatabaseConnection(); // Жесткая зависимость
}
public function generateReport(): array {
$this->dbConnection->connect();
return $this->dbConnection->getData();
}
}
// Хорошо: High-level класс Report зависит от абстракции, которую также реализует Low-level класс DatabaseConnection
interface DataProvider {
public function getData(): array;
}
class DatabaseConnectionDIP implements DataProvider {
public function connect() { /* ... */ }
public function getData(): array { /* ... */ return ['data from db']; }
}
class ApiConnectionDIP implements DataProvider {
public function getData(): array { /* ... */ return ['data from api']; }
}
class ReportGeneratorDIP {
private DataProvider $dataProvider;
public function __construct(DataProvider $dataProvider) {
$this->dataProvider = $dataProvider; // Зависимость инвертирована через внедрение
}
public function generateReport(): array {
return $this->dataProvider->getData();
}
}
?>
Применение SOLID принципов помогает создавать более понятный, тестируемый и легко расширяемый код.