Назад к вопросам
Middle
97
questionbank

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