PHP协变与逆变

2023-12-07 83

一、简介

PHP协变(Covariance)和逆变(Contravariance)是面向对象编程中的概念,用于描述函数参数类型之间的关系。协变指的是在子类方法中可以使用比父类方法更具体(更窄)的参数类型;逆变则是相反的概念,它允许子类方法使用比父类方法更一般(更宽松)的参数类型。

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

协变使子类比父类方法能返回更具体的类型; 逆变使子类比父类方法参数类型能接受更模糊的类型。在以下情况下,类型声明被认为更具体:

1、在联合类型中删除类型;

2、在交集类型中添加类型;

3、类类型(class type)修改为子类类型;

4、iterable 修改为 array 或者 Traversable。

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

二、协变

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?<a href="https://www.zzbaike.com/tag/php" title="查看所有文章关于 php" target="_blank">php</a>
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";
}
}
<?<a href="https://www.zzbaike.com/tag/php" title="查看所有文章关于 php" target="_blank">php</a> 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"; } }
<?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 类型的新对象。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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();
<?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();
<?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();

以上示例会输出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Ricky meows
Mavrick barks
Ricky meows Mavrick barks
Ricky meows
Mavrick barks

三、逆变

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
}
}
<?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); } }
<?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 类保持不变。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?php
class Dog extends Animal
{
public function eat(Food $food) {
echo $this->name . " eats " . get_class($food);
}
}
<?php class Dog extends Animal { public function eat(Food $food) { echo $this->name . " eats " . get_class($food); } }
<?php

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

下面的例子展示了逆变:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?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);
<?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);
<?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);

以上示例会输出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Ricky eats AnimalFood
Mavrick eats Food
Ricky eats AnimalFood Mavrick eats Food
Ricky eats AnimalFood
Mavrick eats Food

但 $kitty 若尝试 eat() $banana 会发生:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$kitty->eat($banana);
$kitty->eat($banana);
$kitty->eat($banana);

以上示例会输出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
  • 广告合作

  • QQ群号:4114653

温馨提示:
1、本网站发布的内容(图片、视频和文字)以原创、转载和分享网络内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。邮箱:2942802716#qq.com(#改为@)。 2、本站原创内容未经允许不得转裁,转载请注明出处“站长百科”和原文地址。
PHP协变与逆变
上一篇: PHP对象序列化
PHP协变与逆变
下一篇: PHP别名/导入