Интерфейсы в PHP
Сегодня мы поговорим об интерфейсах в PHP. Прежде чем начать разговор непосредственно об интерфейсах, давайте сначала я расскажу вам о том, какую проблему они решают и почему в них появилась необходимость.
Интерфейсы в PHP мы будем изучать на примере геометрических фигур. Пусть у нас есть прямоугольник, квадрат и круг, и, например, мы хотим вычислить их площади. Мы прекрасно помним, что для вычисления площади прямоугольника нам нужно знать длины двух соседних сторон, для квадрата – длину одной стороны, для круга – его радиус. Давайте создадим классы, которые будут описывать свойства этих фигур, а также создадим методы, для вычисления их площади.
Прямоугольник.
class Rectangle
{
private $x;
private $y;
public function __construct(float $x, float $y)
{
$this->x = $x;
$this->y = $y;
}
public function calculateSquare(): float
{
return $this->x * $this->y;
}
}
Квадрат.
class Square
{
private $x;
public function __construct(float $x)
{
$this->x = $x;
}
public function calculateSquare(): float
{
return $this->x ** 2;
}
}
Круг.
class Circle
{
private $r;
public function __construct(float $r)
{
$this->r = $r;
}
public function calculateSquare(): float
{
$pi = 3.1416;
return $pi * ($this->r ** 2);
}
}
Константы класса
Число Пи мы здесь задали в переменную, однако для таких вот постоянных вещей, которые в процессе работы программы не изменяются, лучше использовать константы. Они определяются с помощью слова const. Вот так:
const PI = 3.1416;
Константы принято задавать в самом начале класса и называть их CAPS-ом с подчеркушками. Вот примеры того, как могут называться константы: DB_NAME, COUNT_OF_OBJECTS.
Для того, чтобы обратиться к константе, нужно использовать конструкцию self::ИМЯ_КОНСТАНТЫ, или ИмяКласса::ИМЯ_КОНСТАНТЫ. Ключевое слово self – это обращение к текущему классу (как $this – обращение к текущему объекту, не путайте эти понятия). Константы принадлежат классу, а не его объектам.
Давайте вынесем число Пи в константу.
class Circle
{
const PI = 3.1416;
private $r;
public function __construct(float $r)
{
$this->r = $r;
}
public function calculateSquare(): float
{
return self::PI * ($this->r ** 2);
}
}
Теперь мы можем использовать её и в других методах. Или даже в других классах, обратившись к ней через Circle::PI.
Интерфейсы
Окей, разобрались с константами и имеем в итоге 3 класса, описывающих геометрические фигуры и реализацию для вычисления их площадей. Если присмотреться, то мы видим, что во всех классах определён метод calculateSquare(), возвращающий float. Можно сказать, что у них есть что-то общее.
Допустим, мы хотели бы, чтобы у нас были фигуры, которые умеют считать свою площадь. То есть, говоря чуть более абстрактно, какие-то наши классы обязаны реализовать какой-то внешний интерфейс, а именно – иметь метод calculateSquare(), который всегда возвращает float.
Для этой задачи в PHP есть интерфейсы. Это такие «контракты», которые класс должен соблюдать, если он на это «подписался». А говоря языком программистов, классы могут реализовывать интерфейсы.
Интерфейс – это описание public методов, которые представляют собой только название метода, описание их аргументов и возвращаемый тип. Тело метода в интерфейсе не описывается.
Давайте создадим интерфейс для нашего случая.
interface CalculateSquare
{
public function calculateSquare(): float;
}
Чтобы обязать класс реализовать этот интерфейс нужно использовать слово implements после имени класса.
class Circle implements CalculateSquare
{
...
}
Один класс может реализовывать сразу несколько интерфейсов, в таком случае они просто перечисляются через запятую.
class Circle implements CalculateSquare, Interface2, Interface3
{
...
}
IDE PhpStorm автоматически понимает, что наш класс реализует интерфейс и рисует слева от методов специальные иконки. Если по ним кликнуть, то нас перекинет на интерфейс.
Ну и в интерфейсе если кликнуть на такую иконку, то нам откроется список мест, где этот интерфейс реализован.
Если же мы напишем, что класс реализует какой-то интерфейс, но не реализуем его, то получим ошибку. Об этом нам даже подскажет IDE. Давайте удалим метод calculateSquare() из класса Circle. IDE любезно подчеркнёт красным строку, в которой мы говорим, что класс реализует интерфейс.
Если же мы попробуем запустить этот код, то и вовсе словим фатальную ошибку.
Так что давайте этот метод вернём обратно =)
Что ещё стоит сказать об интерфейсах – один интерфейс может содержать требования по реализации нескольких методов. Они просто перечисляются один за другим, вот так:
interface CalculateSquare
{
public function calculateSquare(): float;
public function getId(): int;
...
}
Но мы пока ограничимся одним методом calculateSquare().
Окей, так для чего это всё?
В программировании зачастую требуется проверить, что перед нами сейчас какой-то конкретный тип объектов, то есть что перед нами экземпляр какого-то класса, либо что этот объект реализует какой-то интерфейс. Для этого используется конструкция instanceof. С её помощью можно понять, является ли объект экземпляром какого-то класса, или реализует интерфейс. Эта конструкция возвращает true или false.
$circle1 = new Circle(2.5);
var_dump($circle1 instanceof Circle);
Результат:
boolean true
Всё верно, объект $circle1 является экземпляром класса Circle. Давайте теперь проверим, является ли он экземпляром класса Rectangle.
$circle1 = new Circle(2.5);
var_dump($circle1 instanceof Rectangle);
Результат:
boolean false
И снова всё верно, он не является экземпляром класса Rectangle.
А теперь давайте проверим, является ли он объектом, класс которого реализует интерфейс CalculateSquare.
$circle1 = new Circle(2.5);
var_dump($circle1 instanceof CalculateSquare);
И мы получим:
boolean true
Вуаля! Теперь мы перед тем как попросить какой-либо объект посчитать свою площадь, можем проверить, есть ли у него такой метод, то есть, реализует ли он соответствующий интерфейс.
Давайте добавим во все наши классы информацию о том, что они реализуют интерфейс CalculateSquare.
class Square implements CalculateSquare
...
class Rectangle implements CalculateSquare
...
class Circle implements CalculateSquare
Давайте теперь насоздаём объектов этих классов и положим их в массив:
$objects = [
new Square(5),
new Rectangle(2, 4),
new Circle(5)
];
Теперь мы можем в цикле пройтись по ним, и для тех, которые реализуют интерфейс, посчитать площадь.
$objects = [
new Square(5),
new Rectangle(2, 4),
new Circle(5)
];
foreach ($objects as $object) {
if ($object instanceof CalculateSquare) {
echo 'Объект реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare();
echo '<br>';
}
}
Результат:
Объект реализует интерфейс CalculateSquare. Площадь: 25
Объект реализует интерфейс CalculateSquare. Площадь: 8
Объект реализует интерфейс CalculateSquare. Площадь: 78.54
Давайте теперь уберём из класса Rectangle упоминание о том, что он реализует этот интерфейс.
class Rectangle
{
...
И снова попробуем запустить код.
Объект реализует интерфейс CalculateSquare. Площадь: 25
Объект реализует интерфейс CalculateSquare. Площадь: 78.54
Как видим, проверка успешно отработала и объект класса Rectangle был пропущен.
Полный код, полученный в ходе урока:
<?php
interface CalculateSquare
{
public function calculateSquare(): float;
}
class Circle implements CalculateSquare
{
const PI = 3.1416;
private $r;
public function __construct(float $r)
{
$this->r = $r;
}
public function calculateSquare(): float
{
return self::PI * ($this->r ** 2);
}
}
class Rectangle
{
private $x;
private $y;
public function __construct(float $x, float $y)
{
$this->x = $x;
$this->y = $y;
}
public function calculateSquare(): float
{
return $this->x * $this->y;
}
}
class Square implements CalculateSquare
{
private $x;
public function __construct(float $x)
{
$this->x = $x;
}
public function calculateSquare(): float
{
return $this->x ** 2;
}
}
$objects = [
new Square(5),
new Rectangle(2, 4),
new Circle(5)
];
foreach ($objects as $object) {
if ($object instanceof CalculateSquare) {
echo 'Объект реализует интерфейс CalculateSquare. Площадь: ' . $object->calculateSquare();
echo '<br>';
}
}
На этом с интерфейсами пока всё. В домашке будет ещё одна интересная функция, которая позволит вам узнать об объектах ещё кое-что.
Комментарии