Пусть у вас есть класс User
, а от
него наследуют классы Employee
и Student
.
При этом предполагается, что вы будете создавать
объекты классов Employee
и Student
,
но объекты класса User
- не будете,
так как этот класс используется только для
группировки общих свойств и методов своих
наследников.
В этом случае можно принудительно запретить
создавать объекты класса User
, чтобы
вы или другой программист где-нибудь их случайно
не создали.
Для этого существуют так называемые абстрактные классы. Абстрактные классы представляют собой классы, предназначенные для наследования от них. При этом объекты таких классов нельзя создать.
Для того, чтобы объявить класс абстрактным,
нужно при его объявлении написать ключевое
слово abstract
:
<?php
abstract class User
{
}
?>
Итак, давайте напишем реализацию абстрактного
класса User
. Пусть у него будет приватное
свойство name
, а также геттеры и сеттеры
для него:
<?php
abstract class User
{
private $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}
?>
Попытка создать объект класса User
вызовет ошибку:
<?php
$user = new User; // выдаст ошибку
?>
А вот унаследовать от нашего класса будет
можно. Сделаем класс Employee
, который
будет наследовать от нашего абстрактного
класса User
:
<?php
class Employee extends User
{
private $salary;
public function getSalary()
{
return $this->salary;
}
public function setSalary($salary)
{
$this->salary = $salary;
}
}
?>
Создадим объект класса Employee
-
все будет работать:
<?php
$employee = new Employee;
$employee->setName('john'); // метод родителя, т.е. класса User
$employee->setSalary(1000); // свой метод, т.е. класса Employee
echo $employee->getName(); // выведет 'john'
echo $employee->getSalary(); // выведет 1000
?>
Аналогично можно создать объект класса Student
,
наследующий от User
:
<?php
class Student extends User
{
private $scholarship;
public function getScholarship()
{
return $this->scholarship;
}
public function setScholarship($scholarship)
{
$this->scholarship = $scholarship;
}
}
?>
Самостоятельно, не подсматривая в мой код,
реализуйте такой же абстрактный класс User
,
а также классы Employee
и Student
,
наследующие от него.
Абстрактные методы
Абстрактные классы также могут содержать абстрактные методы. Такие методы не должны иметь реализации, а нужны для того, чтобы указать, что такие методы должны быть у потомков. А собственно реализация таких методов - уже задача потомков.
Для того, чтобы объявить метод абстрактным,
при его объявлении следует написать ключевое
слово abstract
.
Давайте попробуем на практике. Пусть предполагается,
что все потомки класса User
должны
иметь метод increaseRevenue
(увеличить
доход).
Этот метод должен брать текущий доход пользователя и увеличивать его на некоторую величину, переданную параметром.
Сам класс User
не знает, какой именно
доход будет получать наследник - ведь у работника
это зарплата, а у студента - стипендия. Поэтому
каждый потомок будет реализовывать этот метод
по-своему.
Фишка тут в том, что абстрактный метод класса
User
заставляет программиста реализовывать
этот метод в потомках, иначе PHP выдаст ошибку.
Таким образом вы, или другой программист,
работающий с вашим кодом, никак не сможете
забыть реализовать нужный метод в потомке.
Итак, давайте попробуем на практике. Добавим
абстрактный метод increaseRevenue
в класс User
:
<?php
abstract class User
{
private $name;
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
// Абстрактный метод без тела:
abstract public function increaseRevenue($value);
}
?>
Пусть наш класс Employee
пока останется
без изменений. В этом случае, даже если не
создавать объект класса Employee
,
а просто запустить код, в котором определяются
наши классы, - PHP выдаст ошибку.
Давайте теперь напишем реализацию метода
increaseRevenue
в классе Employee
:
<?php
class Employee extends User
{
private $salary;
public function getSalary()
{
return $this->salary;
}
public function setSalary($salary)
{
$this->salary = $salary;
}
// Напишем реализацию метода:
public function increaseRevenue($value)
{
$this->salary = $this->salary + $value;
}
}
?>
Проверим работу нашего класса:
<?php
$employee = new Employee;
$employee->setName('john'); // установим имя
$employee->setSalary(1000); // установим зарплату
$employee->increaseRevenue(100); // увеличим зарплату
echo $employee->getSalary(); // выведет 1100
?>
Реализуем метод increaseRevenue
и
в классе Student
. Только теперь наш
метод будет увеличивать уже стипендию:
<?php
class Student extends User
{
private $scholarship; // стипендия
public function getScholarship()
{
return $this->scholarship;
}
public function setScholarship($scholarship)
{
$this->scholarship = $scholarship;
}
// Метод увеличивает стипендию:
public function increaseRevenue($value)
{
$this->scholarship = $this->scholarship + $value;
}
}
?>
Добавьте в ваш класс User
такой же
абстрактный метод increaseRevenue
.
Напишите реализацию этого метода в классах
Employee
и Student
.
Добавьте также в ваш класс User
абстрактный
метод decreaseRevenue
(уменьшить
зарплату). Напишите реализацию этого
метода в классах Employee
и Student
.
Некоторые замечания
При наследовании от абстрактного класса, все методы, помеченные абстрактными в родительском классе, должны быть определены в дочернем классе.
При этом область видимости этих методов должна
совпадать или быть менее строгой. Что значит
менее строгой: например, если абстрактный
метод объявлен как protected
, то реализация
этого метода должна быть protected
или public
, но не private
.
Объявления методов также должны совпадать: количество обязательных параметром должно быть одинаковым. Однако класс-потомок может добавлять необязательные параметры, которые не были указаны при объявлении метода в родителе.
Практика
Пусть нам необходимо работать с геометрическими фигурами, например, с квадратами, прямоугольниками, треугольниками и так далее. Пусть каждая фигура будет описываться своим классом, при этом мы хотим сделать так, чтобы каждый класс имел метод для вычисления площади и метод для вычисления периметра фигуры.
Давайте сделаем для этого абстрактный класс
Figure
с двумя абстрактными методами
getSquare
и getPerimeter
.
Почему класс Figure
абстрактный: потому
что он не описывает реально существующую
геометрическую фигуру и, соответственно,
объект этого класса мы не будем создавать.
Почему методы getSquare
и getPerimeter
абстрактные: потому что каждая фигура имеет
свой алгоритм вычисления площади и периметра
и, соответственно, класс Figure
не
может написать реализацию этих методов.
Зачем нам вообще нужен класс Figure
:
чтобы наследовать от него и таким образом
заставить всех наследников реализовать указанные
методы.
Итак, напишем реализацию класса Figure
:
<?php
abstract class Figure
{
abstract public function getSquare();
abstract public function getPerimeter();
}
?>
Пусть теперь мы хотим создать класс Quadrate
для описания геометрической фигуры квадрат.
Как известно, у квадрата все стороны равны,
поэтому для описания квадрата нам нужно задать
только его ширину.
Давайте для этого сделаем приватное свойство
$a
, значение которого будет задаваться
в конструкторе класса:
<?php
class Quadrate
{
private $a;
public function __construct($a)
{
$this->a = $a;
}
}
?>
Давайте теперь унаследуем наш класс Quadrate
от класса Figure
:
<?php
class Quadrate extends Figure
{
private $a;
public function __construct($a)
{
$this->a = $a;
}
}
/*
Код класса не рабочий
и будет выдавать ошибку,
так как мы не написали
реализацию методов родителя.
*/
?>
Сейчас наша реализация класса Quadrate
не рабочая, так как мы не написали реализацию
абстрактных методов родителя.
Давайте сделаем это:
<?php
class Quadrate extends Figure
{
private $a;
public function __construct($a)
{
$this->a = $a;
}
public function getSquare()
{
return $this->a * $this->a;
}
public function getPerimeter()
{
return 4 * $this->a;
}
}
?>
Давайте создадим квадрат со стороной 2
и найдем его площадь и периметр:
<?php
$quadrate = new Quadrate(2);
echo $quadrate->getSquare(); // выведет 4
echo $quadrate->getPerimeter(); // выведет 8
?>
Сделайте аналогичный класс Rectangle
(прямоугольник), у которого будет
два приватных свойства: $a
для ширины
и $b
для длины. Данный класс также
должен наследовать от класса Figure
и реализовывать его методы.
Усложним
Сейчас все методы класса Figure
-
абстрактные. Это, конечно же, не обязательно.
Пусть наш класс имеет еще и метод getRatio
,
который будет находить отношение площади
к периметру (то есть одно делить на второе).
Этот метод уже будет не абстрактный, а иметь реализацию, и все потомки смогут воспользоваться этим методом.
Почему мы можем написать реализацию этого
метода прямо в классе Figure
: потому
что этот метод будет одинаковым для всех потомков.
Итак, добавим наш метод:
<?php
abstract class Figure
{
abstract public function getSquare();
abstract public function getPerimeter();
// Метод для вычисления отношения площади к периметру:
public function getRatio()
{
return $this->getSquare() / $this->getPerimeter();
}
}
?>
Обратите внимание на следующее: хотя методы
getSquare
и getPerimeter
абстрактные
и не имеют реализации, мы их все равно можем
использовать в своем методе getRatio
,
хотя реализация этих методов появится только
в потомках.
Применим наш метод:
<?php
$quadrate = new Quadrate(2);
echo $quadrate->getSquare(); // выведет 4
echo $quadrate->getPerimeter(); // выведет 8
echo $quadrate->getRatio(); // выведет 0.5
?>
Добавьте в класс Figure
метод getSquarePerimeterSum
,
который будет находить сумму площади и периметра.