協(xié)變與逆變

在 PHP 7.2.0 里,通過對子類方法里參數(shù)的類型放寬限制,實現(xiàn)對逆變的部分支持。 自 PHP 7.4.0 起開始支持完整的協(xié)變和逆變。

協(xié)變使子類比父類方法能返回更具體的類型; 逆變使子類比父類方法參數(shù)類型能接受更模糊的類型。

在以下情況下,類型聲明被認為更具體:

如果情況相反,則類型類被認為是模糊的。

協(xié)變

創(chuàng)建一個名為 Animal 的簡單的抽象父類,用于演示什么是協(xié)變。 兩個子類:CatDog 擴展(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";
    }
}

注意:在這個例子中,沒有方法返回了值。 將通過添加個別工廠方法,創(chuàng)建并返回 AnimalCat、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

逆變

繼續(xù)上一個例子,除了 Animal、 Cat、Dog,我們還添加了 FoodAnimalFood 類, 同時為抽象類 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 會發(fā)生什么呢?

$kitty->eat($banana);

以上例程會輸出:

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