Çok Özgüllük ve Az Özgüllük

PHP 7.2.0'da, bir çocuk yöntem değiştirgelerindeki tür sınırlamaları kaldırılarak az özgüllüklere kısmen girildi. PHP 7.4.0'dan itibaren, az ve çok özgüllüklere tam destek eklendi.

Çok özgüllük, bir çocuk yöntemin ebeveyn yönteminden daha özellikli bir tür döndürmesine izin verir. Buna karşın, az özgüllük, bir değiştirgenin bir çocuk yöntemin içinde ebeveyndekinden daha az özellikli olmasına izin verir.

Aşağıdaki durumda tür belirtiminin daha belirgin olduğu varsayılır:

  • Bir union tür için tür adı belirtmemek
  • Bir sınıf türü yerine çocuk sınıf türü belirtmek
  • float yerine int belirtmek
  • iterable yerine array veya Traversable belirtmek
Tersi doğruysa, o tür sınıfının daha az belirgin olduğu varsayılır.

Çok Özgüllük

Çok özgüllüğün nasıl çalıştığını göstermek için, basit bir soyut ebeveyn sınıf olarak Hayvan sınıfı oluşturulup Kedi ve Köpek çocuk sınıflarına genişletildi.

<?php

abstract class Hayvan
{
    protected 
string $isim;

    public function 
__construct(string $isim)
    {
        
$this->name $isim;
    }

    abstract public function 
konuş();
}

class 
Köpek extends Hayvan
{
    public function 
konuş()
    {
        echo 
$this->name " havlar";
    }
}

class 
Kedi extends Hayvan
{
    public function 
konuş()
    {
        echo 
$this->name " miyavlar";
    }
}

Bu örnekte yöntemlerin hiçbiri değer döndürmemektedir. Hayvan, Kedi ve Köpek sınıfı türünde yeni bir nesne döndüren birkaç arayüz eklenecektir.

<?php

interface HayvanYuvası
{
    public function 
sahiplen(string $isim): Hayvan;
}

class 
KediYuvası implements HayvanYuvası
{
    public function 
sahiplen(string $isim): Kedi // Hayvan türünde bir sınıf yerine Kedi türünde bir sınıf döner
    
{
        return new 
Kedi($isim);
    }
}

class 
KöpekYuvası implements HayvanYuvası
{
    public function 
sahiplen(string $isim): Köpek // Hayvan türünde bir sınıf yerine Köpek türünde bir sınıf döner
    
{
        return new 
Köpek($isim);
    }
}

$mırnav = (new KediYuvası)->sahiplen("Tekir");
$mırnav->konuş();
echo 
"\n";

$kuçu = (new KöpekYuvası)->sahiplen("Çomar");
$kuçu->konuş();

Yukarıdaki örneğin çıktısı:

Tekir miyavlar
Çomar havlar

Az Özgüllük

Hayvan, Kedi ve Köpek sınıflarıyla önceki örneğe devam ederek, bunlara Yem ve HayvanYemi sınıflarını dahil edip Hayvan soyut sınıfına ye(HayvanYemi $yem) yöntemini ekleyelim.

<?php

class Yem {}

class 
HayvanYemi extends Yem {}

abstract class 
Hayvan
{
    protected 
string $isim;

    public function 
__construct(string $isim)
    {
        
$this->name $isim;
    }

    public function 
ye(HayvanYemi $yem)
    {
        echo 
$this->name " " get_class($yem) . "yer";
    }
}

Az özgüllüğün davranışını görmek için, herhangi bir Yem türü nesneye izin vermek için Köpek sınıfında ye yöntemi geçersiz kılınır. Kedi sınıfı değişmeden kalır.

<?php

class Köpek extends Hayvan
{
    public function 
ye(Yem $yem) {
        echo 
$this->name " " get_class($yem) . "yer";
    }
}

Aşağıdaki örnek az özgüllüğün davranışını gösterir:

<?php

$mırnav 
= (new KediYuvası)->sahiplen("Tekir");
$kediYemi = new HayvanYemi();
$mırnav->ye($kediYemi);
echo 
"\n";

$kuçu = (new KöpekYuvası)->sahiplen("Çomar");
$kemik = new Yem();
$kuçu->ye($kemik);

Yukarıdaki örneğin çıktısı:

Tekir HayvanYemi yer
Çomar Yem yer

Peki $mırnav $kemik yemeye çalışırsa ne olur?

$mırnav->ye($kemik);

Yukarıdaki örneğin çıktısı:

Fatal error: Uncaught TypeError: Argument 1 passed to Hayvan::ye() must be an instance of HayvanYemi, instance of Yem given
Türkçesi: Ölümcül hata: ... : Hayvan::ye() yöntemine aktarılan 1. değiştirge
bir HayvanYemi örneği olmalı, Yem örneği verildi
add a note add a note

User Contributed Notes 4 notes

up
37
xedin dot unknown at gmail dot com
4 years ago
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`.
up
2
Anonymous
4 years ago
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>';
up
0
maxim dot kainov at gmail dot com
3 years ago
This example will not work:

<?php

class CatFood extends AnimalFood { }

class
Cat extends Animal
{
    public function
eat(CatFood $food) {
        echo
$this->name . " eats " . get_class($food);
    }
}

?>

The reason is:

<?php
   
class DogFood extends AnimalFood { }
  
    function
feedAnimal(Animal $animal, AnimalFood $food) {
       
$animal->eat($food);  
    }

   
$cat = new Cat();
   
$dogFood = new DogFood();  

   
feedAnimal($cat, $dogFood);   
?>

But you can do it with traits, like this:

<?php

trait AnimalTrait
{
    public function
eat(AnimalFood $food)
    {
        echo
$this->name . " ест " . get_class($food);
    }
}

class
Cat
{
    use
AnimalTrait;

    public function
eat(CatFood $food) {
        echo
$this->name . " eats " . get_class($food);
    }
}

?>
up
-2
phpnet-at-kennel17-dotco-dotuk
3 years ago
Following the examples above, you might assume the following would be possible.

<?php

class CatFood extends AnimalFood { ... }

class
Cat extends Animal
{
    public function
eat(CatFood $food) {
        echo
$this->name . " eats " . get_class($food);
    }
}

?>

However, the Liskov Substitution Prinicpal, and therefore PHP, forbids this.  There's no way for cats to eat cat food, if animals are defined as eating animal food.

There are a large number of legitimate abstractions that are forbidden by PHP, due to this restriction.
To Top