G
G
Guy Fawkes2016-09-13 16:03:59
PHP
Guy Fawkes, 2016-09-13 16:03:59

How to use Barbara Liskov's Substitution Principle in PHP?

PHP lacks Double Dispatching and method overloading (and will fail with an error in response to a different signature from the base class/interface). But somehow we need to solve the problem: let's say we have some classes that can be processed by different handlers:

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() {}
}

Services must implement some common methods, but they also have specific methods.
At the same time, the interface of such handlers would like to have a general form
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) {}
}

However, each of the handlers may not work with a specific service implementation.
I considered the following solutions:
- Visitor. It assumes that each of the handlers has methods of type handleService1(Service1 $service) and handleService2(Service2 $service), while one of the methods remains empty
- A mapping array that says which handler can handle which service. Handler interfaces and service interfaces are separated, which corresponds to the segregation of interfaces, but we get the following problems: this array can be stored somewhere or filled in some class from the configuration file, which eliminates possible type hinting both in any method and when iteration over the collection of handlers and leads to the storage of several collections of handlers that differ in interface, as well as several methods of the form addHandler1, addHandler2.
- Transferring to handlers not the service instances themselves, but some results of their work. It leads to the transfer of either objects that are too large and overflowing with getters, or simply arrays, which in no way contributes to hinting and the convenience of writing and maintaining code
At the moment, I don’t see a better solution and I get by with the mapping option, sacrificing hinting when iterating over a set of handlers, and to add them I contain an empty interface, so that at least it would be convenient to find handler classes in the code. It is possible that someone who has encountered the same problem will suggest a good solution to it.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
S
Sergey, 2016-09-13
@guyfawkes

PHP lacks Double Dispatching and method overloading

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

Method overloading:
class Foo {
    public function foo() {}
}

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

what you want to do is called ad-hoc polymorphism, and it's available out of the box in any dynamically typed programming language. It is enough just not to specify the signature explicitly, everything is quite simple) Well, yes, the minus of this is that it is not explicit and in runtime. For languages ​​with static typing, explicit "overloading" is needed only so that the compiler can build the call dispatch tables.
Overloading methods in heirs with a signature change is just the same violation of the Barbara Lisk substitution principle (LSP for short).
> Services must implement some common methods, but they also have specific methods.
take out "common methods" in a separate service and share it as a dependency. Then all services will have only specific methods and then the single responsibility principle (which is characterized as "each object should have only one reason for possible changes") will be achieved.
ps Hungarian notation is a horror. All these prefixes and suffixes that show who the type is (interface, abstract class) are things that break the whole beauty of the idea of ​​​​polymorphism and abstraction. If you want we can talk about it separately.
So polymorphism in our case went for a walk. There are a lot of solutions to this problem, in particular Chain of responsibility.
> visitor. It assumes the presence in each of the handlers of methods like handleService1(Service1 $service) and handleService2(Service2 $service), while one of the methods remains empty.
Why complicate it so much? You should have only one public method outside, and inside the implementation itself will figure it out. Well, that is, if you want - you can make two private methods inside, but it's just as strange.
> A mapping array that says which handler can handle which service.
Again, overcomplication.
In short, your problem is that you have certain services, with a certain interface, which, in fact, do completely different things. That is, they a priori cannot belong to the same type. Well, the LSP violation is obvious, you cannot replace one service implementation with another in the code.
Further options are possible only after you describe at a high level what you need to do. Well, that is, not what you came to, but why you came to this and what the task was originally.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question