Re: [RFC][Discussion] Why can constructors violate LSP?
| From: | Mike Schinkel | Date: | Fri, 24 Nov 2023 04:06:56 +0000 |
| Subject: | Re: [RFC][Discussion] Why can constructors violate LSP? | ||
| References: | 1 2 3 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-121795@lists.php.net to get a copy of this message | ||
> On Nov 23, 2023, at 4:50 PM, Robert Landers <landers.robert@gmail.com> wrote:
>
> On Thu, Nov 23, 2023 at 10:30 PM Deleu <deleugyn@gmail.com> wrote:
>>
>> Constructors are an implementation detail of a specialized class and as such they're
>> not subject to LSP because the goal of LSP is to be able to make sure that any object of a given
>> type hierarchy can be used to accomplish a certain behavior. If you take a step back from PHP's
>> dynamic nature and think about LSP from a more pure type system, the fact you're expecting an
>> object of type C, but then you completely disregard everything about the object itself and dive into
>> it's metadata to build another object, that's the moment you're no longer playing by
>> the rules of OOP. It's like those mathematical equations that prove that 1 = 2, they all have
>> one thing in common: they end up dividing by 0 at some point.
>>
>> OOP here dictates that you should reach for patterns like Builder, Abstract Factory or
>> similar. That way you constraint yourself to the rules of OOP and you won't get weird outcomes.
>>
>> From another point of view, when a type is expected by a function or method, all we can
>> expect from it is whatever was defined as the blueprint (class/interface) of that object and the
>> __construct() is a special method that is not assumed to be part of that blueprint because it's
>> not reasonable to do
$object->__construct(); after receiving an
>> object. As such, a constructor cannot break LSP because the constructor is not part of the
>> object's API from a "receptor" point of view.
>>
>> I don't have a vote so take my opinion with a bucket of salt, but if I could I would
>> definitely vote against such RFC.
>>
>>
>> --
>> Marco Deleu
>
> Thanks Marco,
>
> That's an interesting perspective and one I would agree with for the
> most part, especially if you take my illustration at face value. Where
> it gets weird/breaks down is when you have a class-string, that you
> assert is the correct type, and then try to instantiate it:
>
> // from somewhere
> $class = "C";
>
> if(is_subclass_of($class, P::class)) {
> $example = new $class("world");
> }
>
> If PHP didn't offer these built-in methods, then I would fully agree
> with you, but it does, which puts it into a weird position where
> sometimes a class is substitutable, and in this one special case, it
> is not.
What Marco said pretty much mirrors the answer in Software Engineering SE:
https://softwareengineering.stackexchange.com/a/270738/9114
<https://softwareengineering.stackexchange.com/a/270738/9114>
As if probably obvious now, when you are using a variable containing a class name to instantiate a
class you are actually not using OOP, you are using the text expansion capabilities of PHP. The fact
that it appears to be similar to a method call is just likely coincidence,
Now if classes were first class objects in PHP, then there might be an argument, but alas, they are
not.
Anyway, I always addressed the problem you are running into by defining a make_new()
static method. Here's what your example might look after an initial refactor:
class P {
public $msg;
function __construct($msg){
$this->msg = $msg;
}
}
class C extends P {
static function make_new($props){
return new C($props["msg"]);
}
}
$class = "C";
if(is_subclass_of($class, P::class)) {
$example = $class::make_new( ["msg"=>"world"] );
print_r($example);
}
Of course, getting it to production code will take a lot more, like you can see here (although this
code has now been update for probably five years since I am no longer coding in PHP professionally):
https://github.com/search?q=repo%3Awplib%2Fwplib%20make_new&type=code
<https://github.com/search?q=repo:wplib/wplib%20make_new&type=code>
And if you are dealing with 3rd party classes, you'll need to write wrapper classes.
Hope this helps.
-Mike