В предлагаемом материале освещены вопросы наследования: значение концепции в контексте повторного использования кода, ключевые слова extends и final, перекрытие и видимость.
Повторное
использование кода
Программист, решая ту или иную задачу, может использовать готовый код (функцию, библиотеку, класс), если, конечно, этот код делает все так, как это необходимо. К сожалению, такая ситуация встречается крайне редко. Обычно приходится вносить в готовый код (если он вообще существует) какие-то изменения. Но измененная функция, библиотека или класс перестают быть самими собой; они существуют независимо от своей исходной версии. Если в оригинал будут внесены какие-то усовершенствования,
это никак не отразится на производном от него коде. Синхронизация же всех существующих версий представляется весьма затруднительной. Таким образом, повторное использование кода в указанный способ оказывается не слишком эффективным.
Наследование представляет собой механизм повторного использования кода, не влияющий на исходную версию, но, в то же время, сохраняющей связь между ней и производным
кодом. Таким образом, изменения, внесенные в исходную версию, отразятся и на производной.
Исходный класс, обладающий изначальными свойствами и поведением, называется базовым (base [9]), родительским (parent [8][9]) или суперклассом (superclass [9]). Производный (derived [9]) от него класс, наследующий свойства и поведение родительского класса, а
также обладающий дополнительными и/или измененными свойствами и поведением, называется наследником (дословно - ребенком, дитем - child [8][9]) или субклассом (subclass [8][9]).
Наследование свойств и поведения родительского класса с последующим их частичным изменением и дополнением напоминает наследование генетического материала живыми организмами [9]. Однако, в отличие от биологических объектов (и некоторых языков программирования), в PHP у класса может быть не более одного непосредственного родителя[6].
Ключевое слово extends
Отношения наследования между классами выражаются в PHP ключевым словом extends (подразумевается, что класс-наследник как бы «расширяет» базовый класс).
Пример 9.1. Наследование
<?php
class Foo
{
public $foo = 'Foo';
public function getFoo()
{
return $this->foo;
}
}
?>
<?php
class FooBar extends Foo
{
public $bar = 'Bar';
public function getBar()
{
return $this->bar;
}
}
?>
<?php
$foobar = new FooBar;
print_r($foobar);
?>
FooBar Object
(
[bar] => Bar
[foo] => Foo
)
Благодаря наследованию, объект класса FooBar включает в себя свойство $foo, не определенное в самом классе FooBar (но определенное в родительском
классе Foo).
Благодаря наследованию, объект класса FooBar может использовать метод getFoo(), не определенный в самом классе FooBar
(но определенный в родительском классе Foo).
Класс Foo является родительским классом, а класс FooBar - его наследником.
Видимость при наследовании
В приведенном ранее примере (Пример 9.1, «Наследование») все свойства и методы родительского класса объявлены публичными, поэтому они доступны как из глобального контекста, так и из класса-наследника.
print $foobar->foo;
Foo
print $this->foo; // somewhere within class FooBar
Foo
Объявление свойства или метода защищенным предотвращает доступ к нему из глобального контекста, но сохраняет
доступ из класса-наследника.
Наконец, объявление свойства или метода приватным полностью предотвращает доступ к нему откуда-либо, кроме как из самого класса.
private $foo = 'Foo'; // within class Foo
Однако, в данном случае, все еще можно воспользоваться публичным методом getFoo(). Посколько этот метод остался публичным, доступ к нему сохранен. Сам же метод, будучи определен в классе Foo, имеет доступ к приватному свойству
этого класса.
print $foobar->getFoo();
Foo
print $this->getFoo(); // somewhere within class FooBar
Foo
Перекрытие
При необходимости класс-наследник может повторно определить свойство или метод родительского класса - это называется перекрытием (overriding).
Пример 9.2. Перекрытие
<?php
class Foo
{
public $var;
function __construct($var)
{
$this->var = $var;
}
public function getVar()
{
return $this->var;
}
public function someMethod()
{
print 'This method will be overridden.';
}
}
?>
Неперекрытый конструктор родительского класса присваивает значение свойству текущего объекта. Если текущим является объект класса-наследника, то значение будет присвоено перекрытому свойству этого класса, а не оригинальному свойству
родительского класса.
<?php
class Bar extends Foo
{
public $var = 'Bar';
public function someMethod()
{
print 'This method has been overridden.';
}
}
?>
Родительское свойство перекрывается в классе-наследнике.
Родительский метод перекрывается в классе-наследнике.
<?php
$foo = new Foo('Foo');
$bar = new Bar('Bar');
print $foo->getVar();
print $bar->getVar();
?>
FooBar
Неперекрытый конструктор родительского класса присваисвает значение свойству текущего объекта. Если текущим является объект класса-наследника, то значение будет присвоено перекрытому
свойству этого класса, а не оригинальному свойству родительского класса.
Метод возвращает значение свойства в текущем объекте. Если текущим является объект класса-наследника, то будет возвращено значение перекрытого свойства.
<?php
$foo->someMethod();
$bar->someMethod();
?>
This method will be overridden.
This method has been overridden.
Внимание
При перекрытии в классе-наследнике конструктора следует иметь в виду, что PHP, в отличие от некоторых других
языков, не поддерживает автоматического каскадного вызова родительских конструкторов. При необходимости следует делать это самостоятельно с помощью специальное выражение parent, ссылающееся на родительский класс.
function __construct()
{
parent::__construct();
/* ... */
}
Ключевое слово final
При помощи ключевого слова final можно запретить перекрытие того или иного метода или даже наследование целого класса. Такие классы и методы называются конечными, или финальными.
<?php
class Foo
{
final public function foo()
{
/* ... */
}
/* ... */
}
?>
<?php
final class Bar extends Foo
{
/* ... */
}
?>
Попытка унаследовать конечный класс или перекрыть конечный метод приводит к фатальной ошибке.
public function foo() { /* ... */ } // whithin class Bar
Fatal error: Cannot override final method Foo::foo()
class someClass extends Bar { /* ... */ }
Fatal error: Class someClass may not inherit from final class (Bar)
Контрольные вопросы и задания
Какие из свойств класса someClass будут доступны при его наследовании?
<?php
class someClass
{
public $somePublicProperty;
protected $someProtectedProperty;
private $somePrivateProperty;
/* ... */
}
?>
Как из перекрытого метода someMethod() класса-наследника Bar обратиться к одноименному методу родительского класса Foo (см. Пример 9.2, «Перекрытие»)?
Найдите ошибку и предложите способ ее исправления.
<?php
class A
{
private $a;
function __construct($a)
{
$this->a = $a;
}
public function getA()
{
return $this->a;
}
}
class B extends A
{
private $b;
function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
public function getB()
{
return $this->b;
}
}
$b = new B('A', 'B');
print $b->getA();
print $b->getB();
?>
[6] Отсутствие поддержки множественного наследования компенсируется за счет возможности одновременного применения нескольких интерфейсов (см. Глава 10, Абстрактные классы и интерфейсы).