Эпизод 7. D – DIP: The Dependency Inversion Principle

Привет всем в 7-ом эпизоде сериала “ООП здорового человека”. В этой серии: D – последняя буква аббревиатуры 5-ти принципов SOLID – что он значит? Идемте разбираться.

Теория

Принцип имеет 2 формулировки:

  • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Представим, что есть класс по работе с БД и класс, делающий расчеты на основании того, что прилетело с БД. Таким образом, расчетный модуль ставит в зависимость модуль по работе с БД и , первый- модуль высокого уровня; второй, соответственно, более низкого уровня. Собственно, данный принцип – это следование принципу LSP и OSP.

Примеры

1: рассмотрим все тот же пример с БД и расчетами: однажды, по какой-нибудь причине может понадобиться переключится на иную БД.

№2: ближе к жизни. Человек передвигался на велосипеде, нафармил денег – купил машину. Здесь уже DIP сохранен, постольку, и велик, и тачка, созданы для управление руками и ногами.

Практика

Рассмотрим пример с велосипедом и машиной. В самом простом случаи(без всяких принципов), начальная позиция выглядит так

<?php
class Human
{
    function move()
    {
        $transport = new Bicycle();
        
        $transport->getMaxSpeed();
    }
}

class Bicycle
{
    function getMaxSpeed()
    {}
}

затем мы класс Bicycle заменяем классом Car. В моем коде это одноо место, в боевом коде же- их будет много; лазить по коду и везде переписывать – как минимум – долго(не говоря о возникших багах). Следующей ступенью развития может быть использование Внедрение зависимостей (Dependency injection)

<?php
class Human
{
    public function __construct(private Bicycle $transport)
    {
    }

    function move()
    {
        $this->transport->getMaxSpeed();
    }
}

это уже лучше – один раз на класс это делаем в конструкторе; но если классов где нужно переключить много?

В этом случаи на помощь придет вместо обращения к классу, обращение к интерфейсу(или абстрактному классу) и, по средству позднего связывания, дергать нужный класс

<?php
class Human
{
    public function __construct(private TransportInterface $transport)
    {
    }

    function move()
    {
        $this->transport->getMaxSpeed();
    }
}

class Bicycle implements TransportInterface
{
    function getMaxSpeed()
    {}
}

interface TransportInterface
{
    function getMaxSpeed();
}

таким образом, остается лишь указать класс-реализатор нужного интерфейса.

Эпизод 6. I – ISP: The Interface Segregation Principle

Приветствую вас в 6-ом эпизоде сериала “ООП здорового человека”. Здесь речь пойдет о 4-ем принципе SOLID – разделение интерфейсов. По истории этого принципа мне сказать нечего, потому перейдем к сути.

Теория

Принцип разделения интерфейса. Книга: Создавайте мелкозернистые интерфейсы, ориентированные на клиента – это дословный перевод; если же смысловой – будет звучать так: клиент не должен зависеть от методов, которые он не использует. Из принципа следует, что интерфейс это не принадлежность системы, а должен быть ассоциирован с клиентом и его потребностью.

Примеры

№1 Допустим мы в магазине выбираем товары; у них всех есть общие характеристики(цена, размеры, вес и т.д.);два товара – свитер(дополнительная х-ка материал) и машина(доп. мощность двигла). Принцип о том, что не нужно делать интерфейс со всеми свойствами(и существующими и нет в конкретном классе); нужно сделать 3 интерфейса: интерфейс продукта (с общими свойствами)и специфические к каждому отдельно.

№2 Это уже из моей “программистской” практики. Часть сайта представлял собой анкету, в которой, в зависимости от семейного положения клиента, открывались и закрывались различные блоки полей к заполнению. Было это сделано так: был один фасад(класс) со всеми полями; при выборе положения происходила проверка и по ее результатам некоторые поля в классе устанавливались в NULL. Каюсь, я этот момент не переделывал(времени не было + не мой фронт работ, я всего-лишь глянул что там). Если б я переделывал это в соответствии с ISP, было б где-то так: от фасада отпочковывается 1 интерфейс с общими для всех полями, и еще интерфейсы с индивидуальными полями для каждого состояния.

Практика

Раз уж я вспомнил о примере с работы(на деле это фриланс, без обязательств молчать), рассмотрю его в коде; иными словами – реализую это так, как я вижу(с применением ISP)

Для начала мы имеем общий класс, описывающий все свойства(это его примерный интерфейс)

<?php
interface PersonalDataInterface
{
    public function setFamilyState($value);
    public function setIndividualData($value);
    public function setParentsData($value);
    public function setFamilyData($value);
}

class PersonalData implements PersonalDataInterface
{
    protected int $familyState;
    protected array $individualData;
    protected array $parentsData;
    protected array $familyData;

    public function setFamilyState($value)
    {
        $this->familyData = $value;
    }

    public function setIndividualData($value)
    {
        $this->individualData = $value;
    }

    public function setParentsData($value)
    {
        if ($this->familyState === 0) {
            $this->parentsData = $value;
        }
    }

    public function setFamilyData($value)
    {
        if ($this->familyState === 1) {
            $this->familyData = $value;
        }
    }
}

если очистить код от плевел – было так. После переписи получил это

<?php
interface IndividualDataInterface
{
    public function setIndividualData($value);
}

interface ParentsDataInterface
{
    public function setParentsData($value);
}

interface FamilyDataInterface
{
    public function setFamilyData($value);
}

class MarriedPersonalData implements IndividualDataInterface, FamilyDataInterface
{
    protected array $individualData;
    protected array $familyData;

    public function setIndividualData($value)
    {
        $this->individualData = $value;
    }

    public function setFamilyData($value)
    {
        $this->familyData = $value;
    }
}

class TeenagerPersonalData implements IndividualDataInterface, ParentsDataInterface
{
    protected array $individualData;
    protected array $parentsData;

    public function setIndividualData($value)
    {
        $this->individualData = $value;
    }

    public function setParentsData($value)
    {
        $this->parentsData = $value;
    }

при этом поле familyState(из того как было) становится определителем используемого класса для построения объекта клиента.

На деле – примеров телега! Роли пользователей сайта, типы постов… В целом, это простой совет построения кода для обеспечения его гибкости(как и любой другой принцип). А еще этот принцип соблюдает SRP для интерфейсов.