
2024-05-10
Зачем нужен SOLID и архитектура?
2023-07-24
SOLID - набор принципов или подходов, которые помогают нам писать качественный код. Соблюдая список этих принципов код получается более читаемым и, что самое важное, расширяемым и поддерживаемым. Благодаря этим качественным характеристикам кода - вы сможете расширять функциональность своей системы без особых трудностей. Вы сможете расширять команду разработчиков так, чтобы у них не было отторжения и непонимания кодовой базы вашего продукта. Так же вы сможете сделать продукт поддерживаемым, чтобы в случае необходимости доработок, обновлений или любых других корректировок - это не приводило бы к багам в рандомных частях системы. Стоит сразу упомянуть, что это не является универсальной "таблеткой", которая сделает ваш код идеальным в 100% случаев, но эти подходы явно сделают ваш код чище и лучше.
S - Single responsibility principle - принцип единственной ответственности
O - Open-closed principle - принцип открытости закрытости
L - Barbara Liskov principle - принцип подстановки Барбары Лисков
I - Interface segregation principle - принцип сегрегации интерфейсов
D - Dependency inversion principle - принцип инверсии зависимостей
Single responsibility principle
Суть этой заповеди в том, что каждый класс должен решать только одну конкретную задачу.
<?php
/**
* В такой реализации код НАРУШАЕТ принцип единственной ответственности, так как он и звуки издает
* и из БД данные достает и в БД записывает данные.
*/
class Animal
{
public function __construct(
private readonly string $name
) {
}
public function makeSound(): void
{
echo('make sound'.PHP_EOL);
}
public function getDataFromDb(): array
{
$pdo = new PDO('...');
$animals = $pdo->exec('SELECT * FROM animals');
return $animals;
}
public function saveDataToDb(array $data): void
{
$pdo = new PDO('...');
$animals = $pdo->exec('INSERT INTO animals VALUES');
}
}
<?php
/**
* Вот такая вот реализация, разделенная на 2 класса уже не нарушает принцип единственной ответственности
* так как каждый класс отвечает за свои собственные обязанности. Один издает звуки - другой работает с БД
*/
class Animal
{
public function __construct(
private readonly string $name
) {
}
public function makeSound(): void
{
echo('make sound'.PHP_EOL);
}
}
class AnimalRepository
{
private PDO $pdo;
public function __construct(
array $config
){
$this->pdo = new PDO('...');
}
public function getDataFromDb(): array
{
$animals = $this->pdo->exec('SELECT * FROM animals');
return $animals;
}
public function saveDataToDb(array $data): void
{
$animals = $this->pdo->exec('INSERT INTO animals VALUES');
}
}
Open-closed principle
Данный принцип гласит, что программные сущности ( классы, модули, функции ) должны быть открыты для расширения, но закрыты для модификации. Рассмотрим пример функции:
public function getAnimalSound(string $animal): string
{
if ($animal == 'lion') {
return 'roar';
} elseif ($animal == 'cat') {
return 'meow';
}
return 'php is alive';
}
Проблема в том, что эта функция не открыта для расширения. И если появится новое животное - ее придется модифицировать. Как же исправить эту ситуацию и при этом соблюсти принцип открытости закрытости:
class Animal
{
public function __construct(
private readonly string $name,
private readonly string $sound,
) {
}
public function getAnimalSound(): string
{
return $this->sound;
}
}
Теперь добавив любое другое животное - например собаку - нам не придется редактировать исходный код класса животного и с этим нам помог принцип открытости закрытости.
Barbara Liskov principle
Принцип подстановки Барбары Лисков гласит, что необходимо, чтобы подклассы могли бы служить заменой для своих суперклассов.
Вот пример нарушения принципа внутри функции:
class Animal
{
public function __construct(
private readonly string $name,
private readonly string $sound,
) {
}
public function getAnimalSound(): string
{
return $this->sound;
}
public function getAnimalTailInfo(Animal $animal): array
{
if ($animal instanceof Lion) {
return $animal->getLionTailInfo();
} elseif ($animal instanceof Cat) {
return $animal->getCatTailInfo();
}
return [];
}
}
class Lion extends Animal
{
public function getLionTailInfo(): array
{
return [];
}
}
class Cat extends Animal
{
public function getCatTailInfo(): array
{
return [];
}
}
Функция getAnimalTailInfo фактически зависима от класса, который ее реализует.
А вот так вот будет выглядеть корректная реализация без нарушения принципа Барбары Лисков:
class Animal
{
public function __construct(
private readonly string $name,
private readonly string $sound,
) {
}
public function getAnimalSound(): string
{
return $this->sound;
}
public function getAnimalTailInfo(): array
{
return [];
}
}
class Lion extends Animal
{
public function getAnimalTailInfo(): array
{
return [];
}
}
class Cat extends Animal
{
public function getAnimalTailInfo(): array
{
return [];
}
}
Таким образом мы соблюдаем принцип подстановки Барбары Лисков. Ведь сейчас у нас любой наследник работет по тому же принципу что и родитель. Очень легко обнаружить нарушение данного принципа - в подавляющем большинстве случаев, если в коде встречается instanceOf - то вероятнее всего это нарушение данного принципа.
Interface segregation principle
Принцип гласит, что один интерфейс должен решать одну задачу. В этом плане он похож на принцип единой ответственности. Суть данного принципа в том, что он рекомендует создавать интерфейсы, имеющие только одну зону ответственности.
Пример плохого интерфейса:
interface gadgetInterface
{
public function sendEmail();
public function sendSms();
public function sendSftpFile();
}
Что же в нем плохого? Плохо в нем то, что мы предполагаем, что вообще любой гаджет умеет отправлять и email и sms и файлы по sftp подключению. Мы не предполагаем, что может существовать гаджет, который не умеет что-то из этих действий выполнять. Соответственно если мы встретим такой гаджет, что одной из реализацией его методов будет например такая реализация:
public function sendSftpFile()
{
return;
}
То есть фактически мы поставили заглушку, потому что данный метод нам не нужен. Это не правильно.
Соответственно, чтобы соблюсти принцип разделения интерфейсов - нам надо создать 3 раздельных интерфейса вот так:
interface gadegetSendSmsInterface
{
public function sendSms();
}
interface gadegetSendEmailInterface
{
public function sendEmail();
}
interface gadegetSendSftpFileInterface
{
public function sendSftpFile();
}
Dependency inversion principle
Принцип инверсии зависимостей - объектом зависимости должна быть абстракция, а не конкретика.
<?php
class Lamp
{
public function turnOn()
{
echo('ON'.PHP_EOL);
}
public function turnOff()
{
echo('OFF'.PHP_EOL);
}
}
class PowerSwitch
{
public function __construct(private readonly Lamp $lamp, private bool $on = false) {}
public function toggle()
{
if ($this->on) {
$this->on = false;
$this->lamp->turnOff();
} else {
$this->on = true;
$this->lamp->turnOn();
}
}
}
Проблема данного кода в том, что мы сейчас заточены всего лишь на один класс. Наш PowerSwitch умеет включать только лампочку, но в реальной жизни выключатель может включать и выключать множество разных гаджетов. Решение проблемы к нам приходит из предыдущего принципа с интерфейсами:
interface SwitchableInterface
{
public function turnOn();
public function turnOff();
}
class Lamp implements SwitchableInterface
{
public function turnOn()
{
echo('ON'.PHP_EOL);
}
public function turnOff()
{
echo('OFF'.PHP_EOL);
}
}
class HandyLight implements SwitchableInterface
{
public function turnOn()
{
echo('ONNN'.PHP_EOL);
}
public function turnOff()
{
echo('OFFFFFFFF'.PHP_EOL);
}
}
class PowerSwitch
{
public function __construct(private readonly SwitchableInterface $lamp, private bool $on = false) {}
public function toggle()
{
if ($this->on) {
$this->on = false;
$this->lamp->turnOff();
} else {
$this->on = true;
$this->lamp->turnOn();
}
}
}
При таком подходе мы можем передавать в PowerSwitch любой включаемый и выключаемый гаджет, будучи уверенными, что с ним всё будет хорошо и он будет включаться и выключаться как положено, потому что он реализует интерфейс.
Ура! Я наконец-то дописал статью как собирать собственные бандлы на Symfony 6!!!
Статья про EasyAdmin всё ещё в процессе )))
Не, ну мне же надо на чем-то тестировать твиттер локальный...
Я тут еще много полезного буду выкладывать, так что заходите обязательно почитать.
Сайтик пока что в разработке - это далеко не окончательная версия - по сути это то что удалось слепить за 8 часов.
Комментарии