Files
archived-doc-pt-br/language/oop5/variance.xml
2025-06-06 22:35:10 -03:00

321 lines
8.2 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 2e7c00fd314a708ecbd495ef7cc9ae8c8462c33c Maintainer: leonardolara Status: ready --><!-- CREDITS: leonardolara -->
<sect1 xml:id="language.oop5.variance" xmlns="http://docbook.org/ns/docbook">
<title>Covariância e Contravariância</title>
<para>
No 7.2.0, a contravariância parcial foi introduzida removendo as restrições de tipo
nos parâmetros em um método filho. A partir do PHP 7.4.0, foi adicionado suporte
a covariância e contravariância completas.
</para>
<para>
A covariância permite que um método de uma classe filha retorne um tipo mais específico que o tipo de retorno
do método da classe pai. A contravariância permite a um tipo de parâmetro ser menos
específico em um método de classe filha, em relação à classe pai.
</para>
<para>
Uma declaração de tipo é considerada mais específica nos seguintes casos:
<itemizedlist>
<listitem>
<simpara>
Um tipo é removido de um
<link linkend="language.types.type-system.composite.union">tipo união</link>
</simpara>
</listitem>
<listitem>
<simpara>
Um tipo é adicionado a um
<link linkend="language.types.type-system.composite.intersection">tipo interseção</link>
</simpara>
</listitem>
<listitem>
<simpara>
Um tipo de classe é alterado para um tipo de classe filha
</simpara>
</listitem>
<listitem>
<simpara>
<type>iterable</type> é alterado para <type>array</type> ou <classname>Traversable</classname>
</simpara>
</listitem>
</itemizedlist>
Uma classe de tipo é considerada menos específica se o oposto for verdadeiro.
</para>
<sect2 xml:id="language.oop5.variance.covariance">
<title>Covariância</title>
<para>
Para ilustrar como uma variância funciona, uma classe pai abstrata simples, <varname>Animal</varname>
é criada. <varname>Animal</varname> será estendida por classes filhas,
<varname>Cat</varname> e <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>
Observe que não há nenhum método que retorne valores neste exemplo. Serão adicionados
alguns métodos que retornam um novo objeto do tipo de classe <varname>Animal</varname>,
<varname>Cat</varname> ou <varname>Dog</varname>.
</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 // em vez de retornar o tipo Animal, pode retornar o tipo Cat
{
return new Cat($name);
}
}
class DogShelter implements AnimalShelter
{
public function adopt(string $name): Dog // em vez de retornar o tipo Animal, pode retornar o tipo 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>Contravariância</title>
<para>
Continuando com o exemplo anterior com as classes <varname>Animal</varname>,
<varname>Cat</varname> e <varname>Dog</varname>, duas classes chamadas
<varname>Food</varname> e <varname>AnimalFood</varname> serão incluídas, e
um método <varname>eat(AnimalFood $food)</varname> é adicionado à classe abstrata
<varname>Animal</varname>.
</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 . " eats " . get_class($food);
}
}
]]>
</programlisting>
</informalexample>
<para>
Para ver o comportamento da contravariância, o método
<varname>eat</varname> é substituído na classe <varname>Dog</varname> para permitir
qualquer objeto do tipo <varname>Food</varname>. A classe <varname>Cat</varname> permanece inalterada.
</para>
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
class Dog extends Animal
{
public function eat(Food $food) {
echo $this->name . " eats " . get_class($food);
}
}
]]>
</programlisting>
</informalexample>
<para>
O próximo exemplo irá mostrar o comportamento da contravariância.
</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 eats AnimalFood
Mavrick eats Food
]]>
</screen>
<para>
Mas o que acontece se <varname>$kitty</varname> tentar comer (<methodname>eat</methodname>) a
<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>
<sect2>
<title>Variância de propriedade</title>
<simpara>
Por padrão, as propriedades não são covariantes nem contravariantes e, portanto, invariantes.
Ou seja, o tipo delas não pode mudar em nenhuma classe filha.
A razão para isso é que as operações "get" devem ser covariantes
e as operações "set" devem ser contravariantes.
A única maneira de uma propriedade satisfazer ambos os requisitos é ser invariante.
</simpara>
<simpara>
A partir do PHP 8.4.0, com a adição de propriedades abstratas (em uma interface ou classe abstrata) e
<link linkend="language.oop5.property-hooks.virtual">propriedades virtuais</link>,
é possível declarar uma propriedade que possui apenas uma operação get ou set.
Como resultado, propriedades abstratas ou propriedades virtuais que exigem apenas uma operação "get" podem ser covariantes.
Da mesma forma, uma propriedade abstrata ou propriedade virtual que requerem apenas uma operação "set" pode ser contravariante.
</simpara>
<simpara>
Uma vez que uma propriedade tenha uma operação get e set, entretanto,
ela não será mais covariante ou contravariante para extensão adicional.
Ou seja, agora é invariante.
</simpara>
<example>
<title>Variância do tipo de propriedade</title>
<programlisting role="php">
<![CDATA[
<?php
class Animal {}
class Dog extends Animal {}
class Poodle extends Dog {}
interface PetOwner
{
// Apenas uma operação get é necessária, portanto isso pode ser covariante.
public Animal $pet { get; }
}
class DogOwner implements PetOwner
{
// Este pode ser um tipo mais restritivo, já que o lado "get"
// ainda retorna um Animal. Porém, como propriedade nativa,
// os filhos desta classe não podem mais alterar o tipo.
public Dog $pet;
}
class PoodleOwner extends DogOwner
{
// Isso NÃO É PERMITIDO, porque DogOwner::$pet tem operações
// get e set definidas e obrigatórias.
public Poodle $pet;
}
?>
]]>
</programlisting>
</example>
</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
-->