细说 PHP 7.2 子类覆盖方法省略参数类型功能以及 Liskov 替换原则
PHP 7.2 出来也有段时间了,关于新版本有什么新改进,只要你关心 PHP 的发展,应该都看过。这里只细说一个可能会有误解的新功能。
PHP 7.2 可以在当子类覆盖(override)父类方法的时候,忽略父类方法的定义的参数的类型(type hint):
class Foo { public function bar(SomeClass $obj) {} } class Foobar extends Foo { public function bar($obj) {} // 这在 PHP7.2 版本之前是会报错的 }
我看有些网站介绍此功能的时候,说其目的是为了『方便重构。如果以后父类方法的参数类型变了,子类不用再全部换一遍』。听起来好像很有道理。按这说法,隐含的意思是:如果子类忽略了父类方法参数类型,被调用时还是会检查参数类型。实际情况是不是这样做一下实验就知道了:
<?php class Foo { } class Bar { public function setFoo(Foo $foo) { } } class BarKid extends Bar { public function setFoo($foo) { } } $kid = new BarKid; $kid->setFoo('I am a string!');
如果上面的说法是对的,setFoo 接受字符串参数的时候就应该报错,然而上面代码在 7.2 下并没有任何报错信息,但如果子类的 setFoo 方法加上了参数类型,就会立马报错了。记住网上很多说法都不可信,除了我这个小站……
上面的实验说明子类方法可省略参数类型,其目的肯定不是为了方便重构。那真正目的是什么呢?
在 PHP 7.1 里有一个新功能,是『可设置方法或函数的参数和返回类型是否可以为 null』。其中有一条看上去比较别扭的规则:『子类方法参数类型范围放宽(即父类参数若不能为 null ,子类参数可支持 null),但返回类型缩紧(父类若不能返回 null,子类必须也不行;若父类可以返回 null,子类可以不返回 null)』,当时我很简单说了一句,是因为 『Liskov 替换原则』,但没有做深入介绍。身边的 PHPer 们关注 OOP 原则的不多,但我认为它应该被每个工程师知道,还是介绍一下。
Liskov 替换原则简单一句话:父类出现的地方,替换成子类也能运行,即子类可无脑替换父类。其实从语言设计来说,我认为此原则就是对自然规则的模仿2018-09-29 补充:也不是简单的『模仿』,有兴趣可阅读新博客『企鹅不是鸟』。
举个例子,人可以喝酒,喝茶,喝可乐,喝各种饮料,但人作为哺乳动物,怎么着都能喝水吧?但反过来,哺乳动物能喝水,但不一定能喝酒喝茶喝可乐,所以人是哺乳动物的子类。
从语言设计的角度来说,子类就应该是父类的加强版,就是要能比父类处理