공분산과 반공분산
PHP 7.2.0에서 자식 메서드의 매개변수에 대한 유형 제한을 제거하여 부분 반공변성이 도입되었습니다. PHP 7.4.0부터 완전한 공분산(Covariance) 및 반공분산(contravariance) 지원이 추가되었습니다.
공분산(Covariance)을 통해 자식 메서드는 부모 메서드의 반환 유형보다 더 구체적인 형식을 반환할 수 있습니다. 반면 반공변성은 매개변수 유형이 상위 메소드보다 하위 메소드에서 덜 구체적일 수 있도록 합니다.
유형 선언은 다음과 같은 경우에 더 구체적인 것으로 간주됩니다.
- 유형이 공용체 유형에서 제거됨
- 클래스 유형이 자식 클래스 유형으로 변경됨
- iterable은 array 또는 Traversable로 변경됩니다.
반대의 경우 유형 클래스는 덜 구체적인 것으로 간주됩니다.
공분산(Covariance)
공분산이 어떻게 작동하는지 설명하기 위해 간단한 추상 상위 클래스인 Animal이 생성됩니다. Animal은 자식 클래스인 Cat, Dog로 확장됩니다.
<?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 // instead of returning class type Animal, it can return class type Cat
{
return new Cat($name);
}
}
class DogShelter implements AnimalShelter
{
public function adopt(string $name): Dog // instead of returning class type Animal, it can return class type 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
반공분산(Contravariance)
Animal, Cat, Dog 클래스가 있는 이전 예제에 계속해서 Food 및 AnimalFood라는 클래스가 포함되고 Eat(AnimalFood $food) 메서드가 Animal 추상 클래스에 추가됩니다.
<?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);
}
}
반공변성(contravariance)의 동작을 보기 위해 Dog 클래스에서 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가 $banana를 먹으려 하면 어떻게 될까요?
$kitty->eat($banana);
위의 예는 다음을 출력합니다.
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given