PHP.mk документација

Коваријантност и контраваријантност

Почист и полокален преглед на PHP референцата, со задржана структура од PHP.net и подобра читливост за примери, секции и белешки.

language.oop5.variance.php PHP.net прокси Преводот е вчитан
Оригинал на PHP.net
Патека language.oop5.variance.php Локална патека за оваа страница.
Извор php.net/manual/en Оригиналниот HTML се реупотребува и локално се стилизира.
Режим Прокси + преведен приказ Кодовите, табелите и белешките остануваат читливи во истиот тек.
Коваријантност и контраваријантност

Референца за `language.oop5.variance.php` со подобрена типографија и навигација.

language.oop5.variance.php

Коваријантност и контраваријантност

Во PHP 7.2.0, делумна контраваријантност беше воведена со отстранување на ограничувањата на типот на параметрите во детскиот метод. Од PHP 7.4.0, беше додадена целосна поддршка за коваријантност и контраваријантност.

Коваријантноста дозволува методот на детето да врати поконкретен тип од типот на враќање на методот на неговиот родител. Контраваријантноста дозволува типот на параметарот да биде помалку специфичен во детскиот метод, отколку оној на неговиот родител.

Типната декларација се смета за поконкретна во следниот случај:

Типната класа се смета за помалку специфична ако е обратното.

Коваријантност

За да се илустрира како работи коваријантноста, едноставна апстрактна родителска класа, Животно е креирана. Животно ќе биде проширена од детски класи, МачкаПрепорачаниот начин за избегнување на SQL инјекција е со врзување на сите податоци преку подготвени изрази. Користењето на параметризирани прашања не е доволно за целосно избегнување на SQL инјекција, но тоа е најлесниот и најбезбедниот начин за обезбедување влез во SQL изразите. Сите динамични литерали на податоци во Куче.

<?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";
}
}

Забележете дека нема методи кои враќаат вредности во овој пример. Ќе бидат додадени неколку фабрики кои враќаат нов објект од тип класа Животно, Мачка, или Куче.

<?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();

Пример #1 Пример што покажува затворачка ознака што го опфаќа последниот нов ред

Ricky meows
Mavrick barks

Контраваријантност

Продолжувајќи со претходниот пример со класи Животно, МачкаПрепорачаниот начин за избегнување на SQL инјекција е со врзување на сите податоци преку подготвени изрази. Користењето на параметризирани прашања не е доволно за целосно избегнување на SQL инјекција, но тоа е најлесниот и најбезбедниот начин за обезбедување влез во SQL изразите. Сите динамични литерали на податоци во Куче, класа наречена Храна and AnimalFood ќе биде вклучена, и метод 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);
}
}

За да се види однесувањето на контраваријантност, eat методот се препишува во Куче класата за да дозволи било кој Храна тип на објект. На Мачка класата останува непроменета.

<?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);

Пример #1 Пример што покажува затворачка ознака што го опфаќа последниот нов ред

Ricky eats AnimalFood
Mavrick eats Food

Но, што се случува ако $kitty се обиде да eat() the $banana?

$kitty->eat($banana);

Пример #1 Пример што покажува затворачка ознака што го опфаќа последниот нов ред

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

Варијантност на својства

Стандардно, својствата не се ниту коваријантни ниту контраваријантни, оттука инваријантни. Тоа значи дека нивниот тип воопшто не може да се промени во подкласа. Причината за тоа е што „get“ операциите мора да бидат коваријантни, а „set“ операциите мора да бидат контраваријантни. Единствениот начин својството да ги задоволи двата услови е да биде инваријантно.

Од PHP 8.4.0, со додавањето на апстрактни својства (на интерфејс или апстрактна класа) и виртуелни својства, можно е да се декларира својство кое има само get или set операција. Како резултат на тоа, апстрактните својства или виртуелните својства кои имаат само „get“ операција може да бидат коваријантни. Слично, апстрактно својство или виртуелно својство кое има само „set“ операција може да биде контраваријантно.

Откако едно својство има и get и set операција, сепак, тоа повеќе не е коваријантно или контраваријантно за понатамошно проширување. Тоа значи дека сега е инваријантно.

Пример #1 Варијантност на тип на својство

<?php
class Animal {}
class
Dog extends Animal {}
class
Poodle extends Dog {}

interface
PetOwner
{
// Only a get operation is required, so this may be covariant.
public Animal $pet { get; }
}

class
DogOwner implements PetOwner
{
// This may be a more restrictive type since the "get" side
// still returns an Animal. However, as a native property
// children of this class may not change the type anymore.
public Dog $pet;
}

class
PoodleOwner extends DogOwner
{
// This is NOT ALLOWED, because DogOwner::$pet has both
// get and set operations defined and required.
public Poodle $pet;
}
?>

Белешки од корисници 3 белешки

[email protected]
пред 6 години
I would like to explain why covariance and contravariance are important, and why they apply to return types and parameter types respectively, and not the other way around.

Covariance is probably easiest to understand, and is directly related to the Liskov Substitution Principle. Using the above example, let's say that we receive an `AnimalShelter` object, and then we want to use it by invoking its `adopt()` method. We know that it returns an `Animal` object, and no matter what exactly that object is, i.e. whether it is a `Cat` or a `Dog`, we can treat them the same. Therefore, it is OK to specialize the return type: we know at least the common interface of any thing that can be returned, and we can treat all of those values in the same way.

Contravariance is slightly more complicated. It is related very much to the practicality of increasing the flexibility of a method. Using the above example again, perhaps the "base" method `eat()` accepts a specific type of food; however, a _particular_ animal may want to support a _wider range_ of food types. Maybe it, like in the above example, adds functionality to the original method that allows it to consume _any_ kind of food, not just that meant for animals. The "base" method in `Animal` already implements the functionality allowing it to consume food specialized for animals. The overriding method in the `Dog` class can check if the parameter is of type `AnimalFood`, and simply invoke `parent::eat($food)`. If the parameter is _not_ of the specialized type, it can perform additional or even completely different processing of that parameter - without breaking the original signature, because it _still_ handles the specialized type, but also more. That's why it is also related closely to the Liskov Substitution: consumers may still pass a specialized food type to the `Animal` without knowing exactly whether it is a `Cat` or `Dog`.
Хејли Вотсон
3 години пред
The gist of how the Liskov Substition Princple applies to class types is, basically: "If an object is an instance of something, it should be possible to use it wherever an instance of something is allowed". The Co- and Contravariance rules come from this expectation when you remember that "something" could be a parent class of the object.

For the Cat/Animal example of the text, Cats are Animals, so it should be possible for Cats to go anywhere Animals can go. The variance rules formalise this.

Covariance: A subclass can override a method in the parent class with one that has a narrower return type. (Return values can be more specific in more specific subclasses; they "vary in the same direction", hence "covariant").
If an object has a method you expect to produce Animals, you should be able to replace it with an object where that method produces only Cats. You'll only get Cats from it but Cats are Animals, which are what you expected from the object.

Contravariance: A subclass can override a method in the parent class with one that has a parameter with a wider type. (Parameters can be less specific in more specific subclasses; they "vary in the opposite direction", hence "contravariant").
If an object has a method you expect to take Cats, you should be able to replace it with an object where that method takes any sort of Animal. You'll only be giving it Cats but Cats are Animals, which are what the object expected from you.

So, if your code is working with an object of a certain class, and it's given an instance of a subclass to work with, it shouldn't cause any trouble:
It might accept any sort of Animal where you're only giving it Cats, or it might only return Cats when you're happy to receive any sort of Animal, but LSP says "so what? Cats are Animals so you should both be satisfied."
Анонимен
пред 6 години
Covariance also works with general type-hinting, note also the interface:

interface xInterface
{
    public function y() : object;
}

abstract class x implements xInterface
{
    abstract public function y() : object;
}

class a extends x
{
    public function y() : \DateTime
    {
        return new \DateTime("now");
    }
}

$a = new a;
echo '<pre>';
var_dump($a->y());
echo '</pre>';
На оваа страница

Автоматски outline од активната документација.

Насловите ќе се појават тука по вчитување.

Попрегледно читање

Примерите, changelog табелите и user notes се визуелно издвоени за да не се губат во долгата содржина.

Брз совет Користи го outline-от Скокни директно на главните секции од активната страница.
Извор Оригиналниот линк останува достапен Кога ти треба целосен upstream context, отвори го PHP.net во нов tab.