guyfawkes
@guyfawkes
Guy Fawkes

Как использовать Принцип подстановки Барбары Лисков применительно к PHP?

В PHP отсутствует Double Dispatching и перегрузка методов (и в ответ на отличающуюся от базового класса/интерфейса сигнатуру он вывалится с ошибкой). Но решить проблему как-то нужно: пусть у нас есть некие классы, которые могут быть обработаны разными хендлерами:

interface IService
{
  public function methodC();
}

class Service1 implements IService 
{
   public function methodA() {}

   public function methodC() {}
}

class Service2 implements IService
{
   public function methodB() {}

   public function methodC() {}
}


Сервисы должны имплементировать какие-то общие методы, но помимо этого у них есть и специфичные методы.

При этом интерфейс подобных хендлеров хотелось бы иметь общий вида

interface IHandler
{
  public function handle(IService $service);
}

class Handler1 implements IHandler
{
  public function handle(IService $service) {}
}

class Handler2 implements IHandler
{
  public function handle(IService $service) {}
}


Однако каждый из хендлеров может не работать с конкретной реализацией сервиса.

Я рассматривал следующие варианты решения:
- Visitor. Предполагает наличие в каждом из хендлеров методов типа handleService1(Service1 $service) и handleService2(Service2 $service), при этом один из методов остается пустым
- Массив маппинга, который говорит, какой хендлер может обрабатывать какой из сервисов. Интерфейсы хендлеров и интерфейсы сервисов отделяются, что соответствует сегрегации интерфейсов, однако получаем следующие проблемы: этот массив можно где-то хранить или наполнять в каком-то классе из конфигурационного файла, что исключает возможный тайп хинтинг как в каком-либо методе, так и при итерации по коллекции хендлеров и приводит к хранению несколькихт коллекций отличающихся интерфейсом хендлеров, а также нескольким методам вида addHandler1, addHandler2.
- Передача в хендлеры не самих инстансов сервисов, а неких результатов их работы. Приводит к передаче либо слишом больших и переполненных геттерами объектов, либо просто массивов, что никак не способствует хинтингу и удобству написания и поддержки кода

На данный момент я не вижу лучшего решения и обхожусь вариантом с маппингом, жертвуя хинтингом при переборе набора хендлеров, а для их добавления содержу пустой интерфейс, чтобы хотя бы было удобно находить в коде классы хендлеров. Возможно, что кто-нибудь, столкнувшийся с той же проблемой, подскажет хороший вариант ее решения.
  • Вопрос задан
  • 1579 просмотров
Решения вопроса 1
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
В PHP отсутствует Double Dispatching и перегрузка методов


Double Dispatch в PHP:

class Foo {
    // ...
    public function makeSomeStuff(Bar $bar)
    {
         $bar->doStuff($this->someData); // double dispatch!
    }
}


Перегрузка методов:

class Foo {
    public function foo() {}
}

class Bar extends Foo {
    public function foo() {} // перегружен!
}


и в ответ на отличающуюся от базового класса/интерфейса сигнатуру он вывалится с ошибкой


то что вы хотели сделать называется ad-hoc полиморфизм, и он есть из коробки в любом языке программирования с динамической типизацией. Достаточно просто не указывать явно сигнатуру, все довольно просто) Ну и да, минус этого то что это не явно и в рантайме. Для языков со статической типизацией явная "перегрузка" нужна только для того что бы компилятор мог построить таблицы диспетчеризации вызовов.

Но решить проблему как-то нужно


Перегрузка методов в наследниках с изменением сигнатуры это как раз таки нарушение принципа подстановки барбары лисков (LSP для сокращения).

> Сервисы должны имплементировать какие-то общие методы, но помимо этого у них есть и специфичные методы.

выносите "общие методы" в отдельный сервис и шарьте его как зависимость. Тогда у всех сервисов будут только специфичные методы и тогда будет достигаться принцип единой ответственности (который характеризуется как "у каждого объекта должна быть только одна причина для возможных изменений").

p.s. венгерская нотация - это ужас. Все эти префиксы и суффиксы которые показывают кто есть тип (интерфейс, абстрактный класс) это вещи которые ломают всю красоту идеи полиморфизма и абстракции. Если хотите можем об этом пообщаться отдельно.

Однако каждый из хендлеров может не работать с конкретной реализацией сервиса.


Значит полиморфизм в нашем случае пошел погулять. Есть куча решений данной проблемы, в частности Chain of responsibility.

> Visitor. Предполагает наличие в каждом из хендлеров методов типа handleService1(Service1 $service) и handleService2(Service2 $service), при этом один из методов остается пустым

зачем так усложнять то? У вас должен быть снаружи только один публичный метод а внутри уже реализация сама разберется. Ну то есть если хотите - можете внутри сделать два приватных метода но это так же странно.

> Массив маппинга, который говорит, какой хендлер может обрабатывать какой из сервисов.

Опять же излишнее усложнение.

Короче ваша проблема в том что у вас есть некие сервисы, с неким интерфейсом, которые по факту делают совсем разные вещи. То есть они априори не могут принадлежать к одному и тому же типу. Ну и нарушение LSP на лицо, вы не можете в коде заменить одну реализацию сервиса другой.

Дальнейшие варианты возможны только после того, как вы опишите на высоком уровне что вам нужно сделать. Ну то есть не то к чему вы пришли а почему вы к этому пришли и какая задача стояла изначально.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы