• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

PHP7.2忽略父类方法以和Liskov替换原则相关问题

武飞扬头像
PHP中文网
帮助18

细说 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 补充:也不是简单的『模仿』,有兴趣可阅读新博客『企鹅不是鸟』。

举个例子,人可以喝酒,喝茶,喝可乐,喝各种饮料,但人作为哺乳动物,怎么着都能喝水吧?但反过来,哺乳动物能喝水,但不一定能喝酒喝茶喝可乐,所以人是哺乳动物的子类。

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

再来说可能有点绕的返回类型,为什么子类要缩小返回的范围呢?其实只要假设一个方法的返回会作为另外一个方法的参数,就很好想了。比如一个『水果饮料厂』类,有一个『生产』方法,返回『水果汁』,并传给了『小朋友』的『喝』方法。有一个『橘子汁工厂』类属于『水果饮料厂』的子类,它的『生产』方法返回类型缩紧,只能返回『橘子汁』,依然给『小朋友』『喝』,并不会出现任何问题。

再举一个反例。如果又出现一个『水果饮料厂』的子类,其『生产』方法除了返回水果汁,还能返回果酿酒,那这个子类很显然不能冒着给小朋友喝酒的风险去替换父类。

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

7.2 的另外一个新功能,是 object 可以作为任何对象的类型。见官方提供例子:

<?php
function test(object $obj) : object
{
    return new SplQueue();
}
test(new StdClass());

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

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

<?php
class Foo
{
}
class FooFoo extends Foo
{
}
class Bar
{
    public function foo(FooFoo $foo)
    {
    }
}
class BarBar extends Bar
{
    public function foo(Foo $foo) // 依然会报『子类不兼容父类方法格式』的错误
    {
    }
}

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

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfchcc
系列文章
更多 icon
同类精品
更多 icon
继续加载