细说 PHP 7.2 子类覆盖方法省略参数类型功能以及 Liskov 替换原则

PHP 7.2 出来也有段时间了,关于新版本有什么新改进,只要你关心 PHP 的发展,应该都看过。这里只细说一个可能会有误解的新功能。

PHP 7.2 可以在当子类覆盖(override)父类方法的时候,忽略父类方法的定义的参数的类型(type hint):

我看有些网站介绍此功能的时候,说其目的是为了『方便重构,如果以后父类此方法的参数类型变了,子类不用再全部换一遍』。听起来好像很有道理。不过按这个说法,即意味着即使被覆写的方法不标明参数的类型,在被调用时也应该检查参数类型。但实际情况是不是这样呢?我们亲自做一下实验:

在 7.2 下的运行结果是并没有任何报错信息,但如果子类的 setFoo 方法加上了参数类型,就会立马报错了。

这说明被覆写的方法可省略参数类型,其目的不是为了方便重构。既然如此,那其真正目的是什么呢?

在 PHP 7.1 里有一个新功能,是『可设置方法或函数的参数和返回类型是否可以为 null』。其中对于看上去比较别扭的『子元素方法参数类型范围放宽(父类参数若不能为 null ,子类参数可支持 null),但返回类型缩紧(父类若不能返回 null,子类必须也不行;若父类可以返回 null,子类可以不返回 null)』的规则,当时我就写了一句是因为 『Liskov 替换原则』的原因,并没有做深入介绍,但似乎 PHPer 关注此原则的人不多,但我认为它应该被每个工程师知道,还是介绍一下。

Liskov 替换原则简单一句话:如果父类对象的某个方法能处理某个类型的对象,替换成子类对象来处理这个类型的对象时,也不能出现处理不了的情况。必须做到子类可无脑替换父类。其实从语言设计来说,我认为此原则就是对自然规则的模仿。

举个例子,人可以喝酒,喝茶,喝可乐,喝各种饮料,但人作为哺乳动物,怎么着起码首先都能喝水吧?但反过来,哺乳动物能喝水,但不一定能喝酒喝茶喝可乐……

总之,从语言设计的角度来说,子类就是被设计成父类的加强版,就是要能比父类处理更多的对象类型,,而被覆写的方法参数类型的扩大,就是这一原则的体现。

在来说可能有点绕的返回类型。其实只要假设一个方法的返回数据会被另外一个对象用到,就好想了。比如一个饮料厂,可以生产各种水果饮料(饮料厂的返回类型),给小朋友们(上面所说的『另外一个对象』)喝没问题。如果把饮料厂换成一个子类厂,只能生产橘子汁,那小朋友只能喝到橘子汁了,但也没啥问题。如果把饮料厂再换成另外一个子类厂,除了生产水果汁还能生产啤酒,那小朋友就有可能喝到啤酒,自然就不合理了。

说完了 Liskov 替换原则,我们再来看看 7.2 里的这个改进,我们这时应该知道其实这也是 Liskov 原则的体现。目前来说,替换原则在 PHP 的实现并不完全。可能有人觉得这个版本是不是也支持『父类没有返回类型,子类可以有返回类型』呢?遗憾的是至少在 7.2 这个版本,并不支持,大家可以自行实验一下。

7.2 的另外一个新功能,也就是 object 可以作为所有种类对象的类型,其实在 7.2 发布之前,也是出于替换原则,有过一次关于『是否子类可以用 object 类型来替代被覆盖的方法对象参数的类型』,但最终投票并没有通过。虽然我不知道原因,但起码有人提了。

另外目前 PHP 不能像 Java 那样重载(overload),没有办法可以指定覆盖的方法的类型(目前只能把类型直接去掉,有点太粗暴):

但 PHP 也在不断的变化不是吗?最近 PHP 版本迭代这么快,我对 PHP 成为一个支持更多 OOP 特性的语言还是非常有信心的!

wx donate

打赏的男神女神们

送你阿里云优惠券

CC BY-NC-ND 4.0 细说 PHP 7.2 子类覆盖方法省略参数类型功能以及 Liskov 替换原则 by Chrisyue's Blog is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

发表评论

电子邮件地址不会被公开。

× eight = 16