Концепция SOLID в ООП

Автор: Eugeny Nosenko
Дата публикации: 2023-05-05 11:31:59   1042


Концепция SOLID в ООП

Концепция SOLID является одной из самых важных концепций объектно-ориентированного программирования (ООП). SOLID — это аббревиатура, которая означает пять принципов:

  • S - Принцип единственной ответственности (Single Responsibility Principle)
  • О - Принцип открытости/закрытости (Open/Closed Principle)
  • L - Принцип подстановки Барбары Лисков (Liskov Substitution Principle)
  • I - Принцип разделения интерфейса (Interface Segregation Principle)
  • D - Принцип инверсии зависимостей (Dependency Inversion Principle)

Применение этих принципов помогает создавать более чистый, гибкий и легко поддерживаемый код. В этой статье мы рассмотрим каждый принцип SOLID и расскажем, как его можно применять в PHP.

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

Принцип единственной ответственности гласит, что каждый класс должен иметь только одну ответственность. Если класс имеет более одной ответственности, то изменения в одной из них могут повлиять на другие, что приводит к нежелательным побочным эффектам. Поэтому классы должны быть разделены на более мелкие, каждый из которых имеет только одну ответственность.

Пример (php):

class UserManager {
  public function register($name, $email, $password) {
    // регистрация пользователя
  }

  public function login($email, $password) {
    // авторизация пользователя
  }

  public function sendEmail($email, $message) {
    // отправка email пользователю
  }
}

Класс UserManager имеет три ответственности - регистрацию пользователя, авторизацию пользователя и отправку email. Лучше разделить его на три разных класса, каждый из которых будет иметь только одну ответственность.

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

Принцип открытости/закрытости гласит, что классы должны быть открыты для расширения, но закрыты для изменения. Это означает, что изменения поведения класса должны происходить через добавление нового кода, а не изменение существующего.

Пример (php):

interface PaymentMethod {
  public function pay($amount);
}

class CreditCardPayment implements PaymentMethod {
  public function pay($amount) {
    // оплата кредитной картой
  }
}

class PayPalPayment implements PaymentMethod {
  public function pay($amount) {
    // оплата через PayPal
  }
}

class Order {
  private $paymentMethod;

  public function __construct(PaymentMethod $paymentMethod) {
    $this->paymentMethod = $paymentMethod;
  }

  public function checkout($amount) {
    $this->paymentMethod->pay($amount);
  }
}

В этом примере класс Order имеет одну ответственность - оформление заказа. Он принимает в качестве аргумента объект, реализующий интерфейс PaymentMethod, который используется для оплаты заказа. Если необходимо добавить новый способ оплаты, например, оплату через Google Pay, то нужно создать новый класс, реализующий интерфейс PaymentMethod, а не изменять существующий код.

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

Принцип подстановки Барбары Лисков гласит, что объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы. Это означает, что если у нас есть класс, реализующий интерфейс или наследующий другой класс, то мы должны иметь возможность использовать объекты этого класса везде, где ожидается объект интерфейса или родительского класса.

Пример (php):

class Animal {
  public function speak() {
    echo "Animal speaks";
  }
}

class Dog extends Animal {
  public function speak() {
    echo "Dog barks";
  }
}

class Cat extends Animal {
  public function speak() {
    echo "Cat meows";
  }
}

function makeAnimalSpeak(Animal $animal) {
  $animal->speak();
}

$dog = new Dog();
$cat = new Cat();
makeAnimalSpeak($dog); // выводит "Dog barks"
makeAnimalSpeak($cat); // выводит "Cat meows"

Функция makeAnimalSpeak принимает объект типа Animal, но мы можем передать в нее объекты классов Dog и Cat, которые наследуют Animal, и функция все равно будет работать правильно. Это означает, что мы можем заменить объект базового класса на объект любого из его подтипов без изменения поведения программы.

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

Принцип разделения интерфейса гласит, что клиенты не должны зависеть от методов, которые они не используют. Если интерфейс содержит много методов, которые не нужны клиенту, то лучше разделить интерфейс на более мелкие, каждый из которых будет иметь только те методы, которые необходимы клиенту.

Пример (php):

interface Car {
  public function startEngine();
  public function stopEngine();
  public function turnLeft();
  public function turnRight();
  public function accelerate();
  public function brake();
}

class SportsCar implements Car {

  public function startEngine() {
    // запуск мощного двигателя
  }

  public function stopEngine() {
    // остановка двигателя
  }

  public function turnLeft() {
    // поворот налево
  }

  public function turnRight() {
    // поворот направо
  }

  public function accelerate() {
    // ускорение
  }

  public function brake() {
    // торможение
  }
}

class CityCar implements Car {

  public function startEngine() {
    // запуск экономичного двигателя
  }

  public function stopEngine() {
    // остановка двигателя
  }

  public function turnLeft() {
    // поворот налево
  }

  public function turnRight() {
    // поворот направо
  }

  public function accelerate() {
    // ускорение
  }

  public function brake() {
    // торможение
  }
}

В этом примере интерфейс Car содержит все методы, которые могут использоваться в любом типе автомобиля. Однако, если мы создаем класс SportsCar, который не имеет нужды в методах turnLeft и turnRight, то лучше разделить интерфейс на два более мелких - Engine и Steering, каждый из которых будет иметь только те методы, которые необходимы для соответствующей функциональности автомобиля.

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

Принцип инверсии зависимостей гласит, что модули верхнего уровня не должны зависеть от модулей нижнего уровня, а оба типа модулей должны зависеть от абстракций. Это означает, что классы должны зависеть от абстракций, а не от конкретных реализаций.


Пример (php):

interface DatabaseInterface {
  public function connect();
  public function disconnect();
  public function query($sql);
}

class MySQL implements DatabaseInterface {
  public function connect() {
  }

  public function disconnect() {
  }

  public function query($sql) {
  }
}

class DbAdapter {
    public function __construct(private DatabaseInterface $database) 
}

$dbAdapter = new DbAdapter(new MySQL());

В данном примере в конструкторе класса DbAdapter мы указываем DatabaseInterface в качестве типа передаваемого параметра. То есть адаптер не зависит от конкретной реализации и сможет работать с любой БД.   


Оставить комментарий:
Имя:
Комментарий: