php-doc-en/language/oop5/variance.xml
Peter Cowburn a8cc01634c k
git-svn-id: https://svn.php.net/repository/phpdoc/en/trunk@348562 c90b9560-bf6c-de11-be94-00142212c4b1
2019-12-17 20:24:03 +00:00

243 lines
5.7 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<sect1 xml:id="language.oop5.variance" xmlns="http://docbook.org/ns/docbook">
<title>Covariance and Contravariance</title>
<para>
Prior to PHP 7.4.0, class methods support mainly invariant parameter types and invariant return types.
This means that if a method within a parent class has a parameter type or return
type of <varname>T</varname>, any child method with corresponding parameter type
or return type <emphasis>must also</emphasis> be type <varname>T</varname>.
</para>
<para>
As of PHP 7.4.0, covariance and contravariance is supported. While these concepts
may <emphasis>sound</emphasis> confusing, in practice they're rather simple, and
extremely useful to object-oriented programming.
</para>
<sect2 xml:id="language.oop5.variance.covariance">
<title>Covariance</title>
<para>
Covariance allows a child's method to return a more specific type than the return
type of its parent's method. This is better illustrated with an example.
</para>
<para>
We'll start with a simple abstract parent class, <varname>Animal</varname>, which is
extended by children classes, <varname>Cat</varname>, and <varname>Dog</varname>.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?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";
}
}
]]>
</programlisting>
</informalexample>
<para>
Note that there aren't any methods which return values in this example. We will
build upon these classes with a few factories which return a new object of class type
<varname>Animal</varname>, <varname>Cat</varname>, or <varname>Dog</varname>.
Covariance will come into play in the next example.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?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();
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Ricky meows
Mavrick barks
]]>
</screen>
</informalexample>
</sect2>
<sect2 xml:id="language.oop5.variance.contravariance">
<title>Contravariance</title>
<para>
Contravariance, on the other hand, allows a parameter type to be less specific in a child
method, than that of its parent. Continuing with our previous example with the classes
<varname>Animal</varname>, <varname>Cat</varname>, and <varname>Dog</varname>, we're adding
a class called <varname>Food</varname> and <varname>AnimalFood</varname>, and adding a method
<varname>eat(AnimalFood $food)</varname> to our <varname>Animal</varname> abstract class.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?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 . " nom noms " . get_class($food);
}
}
]]>
</programlisting>
</informalexample>
<para>
In order to see the behavior of contravariance, we will override the
<varname>eat</varname> method in the <varname>Dog</varname> class to allow any
<varname>Food</varname> type object. The <varname>Cat</varname> class remains unchanged.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Dog extends Animal
{
public function eat(Food $food) {
echo $this->name . " nom noms " . get_class($food);
}
}
]]>
</programlisting>
</informalexample>
<para>
Now, we can see how contravariance works.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?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);
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Ricky nom noms AnimalFood
Mavrick nom noms Food
]]>
</screen>
<para>
But what happens if <varname>$kitty</varname> tries to <varname>eat</varname> the
<varname>$banana</varname>?
</para>
<programlisting role="php">
<![CDATA[
$kitty->eat($banana);
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Fatal error: Uncaught TypeError: Argument 1 passed to Animal::eat() must be an instance of AnimalFood, instance of Food given
]]>
</screen>
</informalexample>
</sect2>
</sect1>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->