Полиморфизм в PHP

В этом уроке мы поговорим о последнем из трёх китов ООП – полиморфизме. В этом уроке нам придется отойти от примеров с котиками и собачками, и немного удариться в абстракции.

Однако перед этим давайте вспомним конструкцию instanceof. Она позволяет узнать, является ли объект экземпляром какого-то класса, либо что он реализует какой-либо интерфейс. Возвращает true или false.

class A
{
    public function sayHello()
    {
        return 'Hello, I am A';
    }
}

$a = new A();

var_dump($a instanceof A); // true

Давайте создадим ещё один класс, который будет наследником класса A, и выполним ту же проверку для объекта этого класса.

class B extends A
{
}

$b = new B();

var_dump($b instanceof B); // true

При этом если проверить объект дочернего класса, является ли он объектом родительского класса, то мы получим true.

class B extends A
{
}

$b = new B();

var_dump($b instanceof A); // тоже true!

Это правило работает только в одну сторону, объекты родительского класса не будут являться экземплярами дочернего.

class A
{
    public function sayHello()
    {
        return 'Hello, I am A';
    }
}

class B extends A
{
}

$a = new A();
$b = new B();

var_dump($a instanceof B); // false

Согласитесь, это вполне логично.

Таким образом объекты дочерних классов будут проходить проверку на то, что они являются экземплярами родительских классов.

Как мы помним, методы объектов родительских классов у нас доступны и в дочерних – они наследуются. Соответственно мы можем быть уверены, что эти же методы есть и у дочерних объектов. Конкретно в нашем примере – у объектов класса B будет метод sayHello(), унаследованный от A.

Или в примере с интерфейсами из прошлого урока – мы определили, что объекты, реализовавшие какой-то интерфейс обязательно будут иметь метод с определённым набором параметров. То есть мы можем рассчитывать на то, что этот метод у объекта гарантированно есть и мы можем его вызвать.

Благодаря этому свойству объектов мы можем ожидать от них какого-то определенного поведения, если они являются объектами какого-то класса или реализуют какой-то интерфейс.

Это свойство, благодаря которому мы можем зависеть не от конкретной реализации (проще говоря конкретного класса), а от абстракции (интерфейса), и есть – полиморфизм.

Да-да, в примере про вычисление площадей фигур мы уже сталкивались с полиморфизмом! В тот момент, когда мы проверяли, имеется ли у объекта интерфейс для вычисления площади, мы работали с проявлением полиморфизма.

Переопределение методов

Как мы уже сказали, в дочерних объектах доступны методы объектов родительского класса.

class A
{
    public function sayHello()
    {
        return 'Hello, I am A';
    }
}

class B extends A
{
}

$b = new B();

echo $b->sayHello(); // Hello, I am A

Однако, мы можем переопределить этот метод в классе B. Для этого мы описываем метод с таким же названием и описываем в нём свою логику:

class A
{
    public function sayHello()
    {
        return 'Hello, I am A';
    }
}

class B extends A
{
    public function sayHello()
    {
        return 'Hello, I am B';
    }
}

$b = new B();

echo $b->sayHello(); // 'Hello, I am B

Мы также можем вызвать родительский метод в дочернем при помощи слова parent и двойного двоеточия.

<?php

class A
{
    public function sayHello()
    {
        return 'Hello, I am A';
    }
}

class B extends A
{
    public function sayHello()
    {
        return parent::sayHello() . '. It was joke, I am B :)';
    }
}

$b = new B();

echo $b->sayHello(); // Hello, I am A. It was joke, I am B :)

Мы вызвали родительский метод, и дополнили его функционал. Так часто приходится делать в реальных проектах. Буквально через пару уроков вы столкнетесь с более понятными примерами того, для чего всё это нужно. Но пока что мы должны изучить базу на немного искусственных примерах. К сожалению, придумать что-то более интересное я не смог.

И ещё один примерчик, тоже искуственный.

class A
{
    public function method1()
    {
        return $this->method2();
    }

    protected function method2()
    {
        return 'A';
    }
}

class B extends A
{
    protected function method2()
    {
        return 'B';
    }
}

$b = new B();

echo $b->method1();

Как думаете, что выведет этот код?

A или B?

Не знаю, что вы ответили, но он в любом случае выведет B. Внутри метода method1() будет вызван тот метод method2(), который определён для класса, в котором его выполняют. То есть $this не привязан напрямую к классу A. Когда мы вызываем method1() у экземпляра класса B, то $this внутри него будет связан с классом B, и вызовется метод method2(), определенный именно в классе B.

И это – тоже полиморфизм. $this привязывается к объекту, в котором код выполняется, а не там, где он был описан.

На этом с полиморфизмом пока всё, если что-то непонятно – пишите в комментах.

loader
Логические задачи с собеседований