codecamp

PHP8 协变与逆变

在 PHP 7.2.0 里,通过对子类方法里参数的类型放宽限制,实现对逆变的部分支持。 自 PHP 7.4.0 起开始支持完整的协变和逆变。

协变使子类比父类方法能返回更具体的类型; 逆变使子类比父类方法参数类型能接受更模糊的类型。

在以下情况下,类型声明被认为更具体:

  • 在 联合类型 中删除类型
  • 在 交集类型 中添加类型
  • 类类型(class type)修改为子类类型
  • iterable 修改为 array 或者 Traversable

如果情况相反,则类型类被认为是模糊的。

协变

创建一个名为 Animal 的简单的抽象父类,用于演示什么是协变。 两个子类:Cat 和 Dog 扩展(extended)了 Animal。

<?php

abstract class Animal
{
protected string $name;

public function __construct(string $name)
{
$this->name = $name;
}

abstract public function speak();
}

class Dog extends Animal
{
public function speak()
{
echo $this->name . " barks";
}
}

class Cat extends Animal
{
public function speak()
{
echo $this->name . " meows";
}
}

注意:在这个例子中,没有方法返回了值。 将通过添加个别工厂方法,创建并返回 Animal、Cat、Dog 类型的新对象。

<?php

interface AnimalShelter
{
public function adopt(string $name): Animal;
}

class CatShelter implements AnimalShelter
{
public function adopt(string $name): Cat // 返回类的类型不仅限于 Animal,还可以是 Cat 类型
{
return new Cat($name);
}
}

class DogShelter implements AnimalShelter
{
public function adopt(string $name): Dog // 返回类的类型不仅限于 Animal,还可以是 Dog 类型
{
return new Dog($name);
}
}

$kitty = (new CatShelter)->adopt("Ricky");
$kitty->speak();
echo "\n";

$doggy = (new DogShelter)->adopt("Mavrick");
$doggy->speak();

以上示例会输出:

Ricky meows
Mavrick barks

逆变

继续上一个例子,除了 Animal、 Cat、Dog,我们还添加了 Food、AnimalFood 类, 同时为抽象类 Animal 添加了一个 eat(AnimalFood $food) 方法。

<?php

class Food {}

class AnimalFood extends Food {}

abstract class Animal
{
protected string $name;

public function __construct(string $name)
{
$this->name = $name;
}

public function eat(AnimalFood $food)
{
echo $this->name . " eats " . get_class($food);
}
}

为了演示什么是逆变,Dog 类重写(overridden)了 eat 方法, 允许传入任意 Food 类型的对象。 而 Cat 类保持不变。

<?php

class Dog extends Animal
{
public function eat(Food $food) {
echo $this->name . " eats " . get_class($food);
}
}

下面的例子展示了逆变。

<?php

$kitty = (new CatShelter)->adopt("Ricky");
$catFood = new AnimalFood();
$kitty->eat($catFood);
echo "\n";

$doggy = (new DogShelter)->adopt("Mavrick");
$banana = new Food();
$doggy->eat($banana);

以上示例会输出:

Ricky eats AnimalFood
Mavrick eats Food

但 $kitty 若尝试 eat() $banana 会发生什么呢?

$kitty->eat($banana);

以上示例会输出:

Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given


PHP8 对象序列化
PHP8 OOP 变更日志
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

PHP8 语言参考

PHP8 函数参考

PHP8 影响 PHP 行为的扩展

PHP8 Componere

PHP8 安装/配置

PHP8 外部函数接口

PHP8 选项和信息

PHP8 选项/信息 函数

PHP8 Windows Cache for PHP

PHP8 WinCache 函数

PHP8 Yac

PHP8 身份认证服务

PHP8 Radius 函数

PHP8 压缩与归档扩展

PHP8 Phar

PHP8 Zip

PHP8 ZipArchive 类

PHP8 加密扩展

PHP8 OpenSSL

PHP8 OpenSSL 函数

PHP8 Sodium 函数

PHP8 数据库扩展

PHP8 针对各数据库系统对应的扩展

PHP8 CUBRID 函数

PHP8 Firebird/InterBase

PHP8 Firebird/InterBase函数

PHP8 MongoDB介绍驱动程序体系结构和特殊功能

PHP8 MongoDB\Driver\Command 类

PHP8 MongoDB\Driver\Query 类

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }