PHP: Наследование

В этой главе:

  • Наследование

  • Оператор parent

  • public, protected и private: управление доступом

Наследование

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

Класс, который получается в результате наследования от другого, называется подклассом. Эту связь обычно описывают с помощью терминов «родительский» и «дочерний». Дочерний класс происходит от родительского и наследует его характеристики: свойства и методы. Обычно в подклассе к функциональности родительского класса (который также называют суперклассом) добавляются новые функциональные возможности.

Чтобы создать подкласс, необходимо использовать в объявлении класса ключевое слово extends, и после него указать имя класса, от которого выполняется наследование:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
   
  class Cat {
    public $age;
     
    function __construct($age) {
      $this->age = $age;
    }
     
    function add_age () {
      $this->age++;
    }
   
  }
   
  // объявляем наследуемый класс
  class my_Cat extends Cat {
    // определяем собственный метод подкласса
    function sleep() {
      echo '<br>Zzzzz...';
    }
  }
 
  $kitty = new my_Cat(10);
   
  // вызываем наследуемый метод
  $kitty->add_age();
   
  // считываем значение наследуемого свойства
  echo $kitty->age;
   
  // вызываем собственный метод подкласса
  $kitty->sleep();
   
?>

Подкласс наследует доступ ко всем методам и свойствам родительского класса, так как они имеют тип public. Это означает, что для экземпляров класса my_Cat мы можем вызывать метод add_age() и обращаться к свойству $age не смотря на то, что они определены в классе cat. Также в приведенном примере подкласс не имеет своего конструктора. Если в подклассе не объявлен свой конструктор, то при создании экземпляров подкласса будет автоматически вызываться конструктор суперкласса.

Обратите внимание на то, что в подклассах могут переопределяться свойства и методы. Определяя подкласс, мы гарантируем, что его экземпляр определяется характеристиками сначала дочернего, а затем родительского класса. Чтобы лучше это понять рассмотрим пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
   
  class Cat {
    public $age = 5;
     
    function foo() {
      echo "$this->age";
    }
  }
   
  class my_Cat extends Cat {
    public $age = 10;
  }
   
  $kitty = new my_Cat;
   
  $kitty->foo();
   
?>

При вызове $kitty->foo() интерпретатор PHP не может найти такой метод в классе my_Cat, поэтому используется реализация этого метода заданная в классе Cat. Однако в подклассе определено собственное свойство $age, поэтому при обращении к нему в методе $kitty->foo(), интерпретатор PHP находит это свойство в классе my_Cat и использует его.

Так как мы уже рассмотрели тему про указание типа аргументов, осталось сказать о том, что если в качестве типа указан родительский класс, то все потомки для метода будут так же доступны для использования, посмотрите на следующий пример:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
   
  class Cat {
    function foo(Cat $obj) {}
  }
    
  class my_Cat extends Cat {}
    
  $kitty = new Cat;
   
  // передаем методу экземпляр класса my_Cat 
  $kitty->foo( new my_Cat );
   
?>

Мы можем обращаться с экземпляром класса my_Cat так, как будто это объект типа Cat, т.е. мы можем передать объект типа my_Cat методу foo() класса Cat, и все будет работать, как надо.

Оператор parent

На практике подклассам бывает необходимо расширить функциональность методов родительского класса. Расширяя функциональность за счет переопределения методов суперкласса, в подклассах вы сохраняете возможность сначала выполнить программный код родительского класса, а затем добавить код, который реализует дополнительную функциональность. Давайте разберем как это можно сделать.

Чтобы вызвать нужный метод из родительского класса, вам понадобится обратиться к самому этому классу через дескриптор. Для этой цели в PHP предусмотрено ключевое слово parent. Оператор parent позволяет подклассам обращаться к методам (и конструкторам) родительского класса и дополнять их существующую функциональность. Чтобы обратиться к методу в контексте класса, используются символы "::" (два двоеточия). Синтаксис оператора parent:

1
parent::метод_родительского_класа

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
   
  class book {
    public $title;
    public $price;
     
    function __construct($title, $price) {
      $this->title = $title;
      $this->price = $price;
    }
  }
   
  class new_book extends book {
    public $pages;
     
    function __construct($title, $price, $pages) {
      // вызываем метод-конструктор родительского класса
      parent::__construct($title, $price);
       
      // инициализируем свойство определенное в подклассе
      $this->pages = $pages;
    }
  }
   
  $obj = new new_book('азбука', 35, 500);
   
  echo "Книга: $obj->title<br>
        Цена: $obj->price<br>
        Страниц: $obj->pages";
   
?>

Когда в дочернем классе определяется свой конструктор, PHP не вызывает конструктор родительского класса автоматически. Это необходимо сделать вручную в конструкторе подкласса. Подкласс сначала в своем конструкторе вызывает конструктор своего родительского класса, передавая нужные аргументы для инициализации, исполняет его, а затем выполняется код, который реализует дополнительную функциональность, в данном случае инициализирует свойство подкласса.

Ключевое слово parent можно использовать не только в конструкторах, но и в любом другом методе, функциональность которого вы хотите расширить, достигнуть этого можно, вызвав метод родительского класса:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
   
  class Cat {
    public $name = "Арни";
     
    function getstr() {
      $str = "Имя кота: {$this->name}.";
      return $str;
    }
  }
   
  class my_Cat extends Cat {
    public $age = 5;
     
    function getstr() {
      $str = parent::getstr();
       
      $str .= "<br>Возраст: {$this->age} лет.";
      return $str;
    }
  }
   
  $obj = new my_Cat;
  echo $obj->getstr();
   
?>

Здесь сначала вызывается метод getstr() из суперкласса, значение которого присваивается переменной, а после этого выполняется остальной код определенный в методе подкласса.

Теперь, когда мы познакомились с основами наследования, можно, наконец, рассмотреть вопрос видимости свойств и методов.

public, protected и private: управление доступом

До этого момента мы явно объявляли все свойства как public (общедоступные). И такой тип доступа задан по умолчанию для всех методов.

Элементы класса можно объявлять как public (общедоступные), protected (защищенные) и private(закрытые). Рассмотрим разницу между ними:

 

  • К public (общедоступным) свойствам и методам, можно получить доступ из любого контекста.

  • К protected (защищенным) свойствам и методам можно получить доступ либо из содержащего их класса, либо из его подкласса. Никакому внешнему коду доступ к ним не предоставляется.

  • Вы можете сделать данные класса недоступными для вызывающей программы с помощью ключевого слова private (закрытые). К таким свойствам и методам можно получить доступ только из того класса, в котором они объявлены. Даже подклассы данного класса не имеют доступа к таким данным.

public - открытый доступ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
   
  class human {
    public $age = 5;
    public function say() {
      echo "<br>hello";
    }  
  }
   
  $obj = new human;
   
  // доступ из вызывающей программы
  echo "$obj->age"// Допустимо
  $obj->say();       // Допустимо
   
?>

private - доступ только из методов класса:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
   
  class human {
    private $age = 5;
    function say() {
      // внутри класса доступ к закрытым данным есть
      echo "$this->age";
    }  
  }
   
  $obj = new human;
   
  // напрямую из вызывающей программы доступа к закрытым данным нет
  echo "$obj->age"// Ошибка! доступ закрыт!
   
  // однако с помощью метода можно выводить закрытые данные
  $obj->say();       // Допустимо
   
?>

protected - защищенный доступ:

Модификатор protected с точки зрения вызывающей программы выглядит точно так же, как и private: он запрещает доступ к данным объекта извне. Однако в отличие от private он позволяет обращаться к данным не только из методов своего класса, но также и из методов подкласса.