Наследование
Наследование является одним из основных аспектов объектно-ориентированного программирования. Наследование позволяет классу взять функционал уже имеющихся классов и при необходимости переопределить его. Если у нас есть какой-нибудь класс, в котором не хватает пары функций, то гораздо проще переопределить имеющийся класс, написав пару строк, чем создавать новый с нуля, переписывая кучу кода.
Чтобы наследовать один класс от другого, нам надо применить оператор extends
. Стоит отметить, что в PHP мы можем унаследовать класс только от одного класса. Множественное наследование не поддерживается.
Например, унаследуем класс Employee
от класса Person
:
<?
class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
function displayInfo() {
echo "Имя: $this->name<br>";
}
}
class Employee extends Person {
}
$tom = new Employee("Tom");
$tom -> displayInfo();
В данном случае предположим, что класс Person
представляет человека в целом, а класс Employee
- работника некого предприятия. В этой связи каждый работник преддставляет человека. И чтобы не дублировать один и тот же функционал, лучше в данном случае унаследовать класс работника - Employee
от класа человека - Person
. В этой паре класс Person
еще называется родительским или базовым классом, а класс - Employee
- производным классом или классом-наследником.
И так как класс Employee
унаследован от Person
, для объектов класса Employee
мы можем использовать функционал родительского класса Person
. Так, для создания объекта Employee
в данном случае вызывается конструктор, который определен в классе Person
и который в качестве параметра принимает имя человека:
$tom = new Employee("Tom");
И также у переменной типа Employee
вызывается метод displayInfo
, который определен в классе Person
:
$tom -> displayInfo();
Переопределение функционала
Унаследовав функционал от родительского класса класс-наследник может добавить свои свойства и методы или переопредилить унаследованный функционал. Например, изменим класс Employee
, добавив в него данные о компании, где работает работник:
<?
class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
function displayInfo() {
echo "Имя: $this->name<br>";
}
}
class Employee extends Person {
public $company;
function __construct($name, $company) {
$this->name = $name;
$this->company = $company;
}
function displayInfo() {
echo "Имя: $this->name<br>";
echo "Работает в $this->company<br>";
}
}
$tom = new Employee("Tom", "Microsoft");
$tom -> displayInfo();
Здесь класс Employee
добавляет новое свойство - $company
, которое хранит компанию работника. Также класс Employee
переопределил конструктор, в который пеередаются данные для имени и компании. А также переопределен метод displayInfo()
. Соответственно для создания объекта класса Employee
, теперь необходимо использовать переопределенный в классе Employee
конструктор:
$tom = new Employee("Tom", "Microsoft");
Класс-наследник переопределяет конструктор родительского класса, то для создания объекта класса-наследника необходимо использовать переопределенный в нем конструктор.
И также изменится поведение метода displayInfo()
, который кроме имени также выведет и компанию работника:
Имя: Tom
Работает в Microsoft
Вызов функционала родительского класса
Если мы посмотрим на код класса-наследника Employee
, то можем увидеть части кода, которые повторяют код класса Person
. Например, установка имени в конструкторе:
$this->name = $name;
Также вывод имени работника в методе displayInfo()
:
echo "Имя: $this->name<br>";
В обоих случаях речь идет об одной строке кода. Однако что, если конструктор Employee
повторяет установку не одного, а десятка свойств. Соответственно что, если метод displayInfo
в классе-наследнике повторяет горадо больше действий родительского класса. В этом случае горадо рациональнее не писать повторяющийся код в классе-наследнике, а вызвать в нем соответствующий функционал родительского класса.
Если нам надо обратиться к методу родительского класса, то мы можем использовать ключевое слово parent
, после которого используется двойное двоеточие ::
и затем вызываемый метод. Например, перепишем предыдущий пример:
<?
class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
function displayInfo() {
echo "Имя: $this->name<br>";
}
}
class Employee extends Person {
public $company;
function __construct($name, $company) {
parent::__construct($name);
$this->company = $company;
}
function displayInfo() {
parent::displayInfo();
echo "Работает в $this->company<br>";
}
}
$tom = new Employee("Tom", "Microsoft");
$tom -> displayInfo();
Теперь в конструкторе Employee
вызывается конструктор базового класса:
parent::__construct($name);
В нем собственно и происходит установка имени. И подобным образом в методе displayInfo()
вызывается реализация метода класса Person
:
parent::displayInfo();
В итоге мы получим тот же самый результат. Стоит отметить, что в реальности ключевое слово parent заменяет название класса. То есть мы также могли вызывать функционал родительского класса через имя этого класса:
<?
class Employee extends Person {
public $company;
function __construct($name, $company) {
Person::__construct($name);
$this->company = $company;
}
function displayInfo() {
Person::displayInfo();
echo "Работает в $this->company<br>";
}
}
Оператор instanceof
Оператор instanceof
позволяет проверить принадлежность объекта определенному класса. Слева от оператора располагается объект, котоый надо проверить, а справа - название класса. И если объект представляет класс, то оператор возвращает true
. Например:
<?
class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
function displayInfo() {
echo "Имя: $this->name<br>";
}
}
class Employee extends Person {
public $company;
function __construct($name, $company) {
Person::__construct($name);
$this->company = $company;
}
function displayInfo() {
Person::displayInfo();
echo "Работает в $this->company<br>";
}
}
class Manager{}
$tom = new Employee("Tom", "Microsoft");
$tom instanceof Employee; // true
$tom instanceof Person; // true
$tom instanceof Manager; // false
Здесь переменная $tom
представляет класс Employee
, поэтому $tom instanceof Employee
возвращает true
.
Так как класс Employee
унаследован от Person
, то переменная $tom
также представляет класс Person
(работник также является человеком).
А вот класс Manager
переменная $tom
не преддставляет, поэтому выражение
$tom instanceof Manager
возвращает false
.
Запрет наследования и оператор final
В примере выше метод displayInfo()
переопределялся классом-наследником. Однако иногда возникают ситуации, когда надо запретить переопределение методов. Для этого в классе-родителе надо указать методы с модификатором final
:
<?
class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
final function displayInfo() {
echo "Имя: $this->name<br>";
}
}
class Employee extends Person {
public $company;
function __construct($name, $company) {
Person::__construct($name);
$this->company = $company;
}
function displayEmployeeInfo() {
Person::displayInfo();
echo "Работает в $this->company<br>";
}
}
$tom = new Employee("Tom", "Microsoft");
$tom -> displayEmployeeInfo();
В этом случае во всех классах-наследниках от класса Person
мы уже не сможем определить метод с таким же именем. Поэтому в данном случае в классе Employee
определен новый метод - displayEmployeeInfo
.
Также мы можем вообще запретить наследование от класса. Для этого данный класс надо определить с модификатором final
:
<?
final class Person {
public $name;
function __construct($name) {
$this->name = $name;
}
final function displayInfo() {
echo "Имя: $this->name<br>";
}
}
Теперь мы не сможем унаследовать класс Employee
(да и никакой другой класс) от класса Person
.