06. Наследование, интерфейсы и абстрактные классы

Введение

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

Эти механизмы позволяют писать чистый, гибкий и расширяемый код.

ООП без наследования — как музыка без мелодии: можно, но скучно.

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

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

Пример наследования


<?php
class User {
  public $name;

public function __construct($name) { $this->name = $name; }

public function hello() { echo 'Привет, ' . $this->name . '!'; } }

class Admin extends User { public function ban($user) { echo 'Пользователь ' . $user . ' заблокирован администратором ' . $this->name . '!'; } }

$admin = new Admin('Алексей'); $admin->hello(); $admin->ban('Иван'); ?>

Вывод: ``` Привет, Алексей! Пользователь Иван заблокирован администратором Алексей! ```

Что здесь происходит

  • Admin — это «расширенная версия» User.
  • Он наследует все свойства и методы класса User.
  • Можно добавлять новые методы (например, ban()).

Переопределение методов

Дочерний класс может изменить (переопределить) метод родителя.

class Moderator extends User {
  public function hello() {
    echo 'Здравствуйте, модератор ' . $this->name . '!';
  }
}

$mod = new Moderator('Сергей'); $mod->hello();

Вывод: ``` Здравствуйте, модератор Сергей! ```

Ключевое слово parent

Если вы хотите вызвать метод родителя внутри дочернего класса:

class SuperAdmin extends Admin {
  public function hello() {
    parent::hello(); // вызывает метод из User
    echo ' (высший уровень доступа)';
  }
}

$sa = new SuperAdmin('Константин'); $sa->hello();

Вывод: ``` Привет, Константин! (высший уровень доступа) ```

Инкапсуляция + наследование

Можно защитить часть данных родителя, используя protected:

class Account {
  protected $balance = 0;

public function deposit($amount) { $this->balance += $amount; } }

class PremiumAccount extends Account { public function bonus() { $this->balance += 100; }

public function show() { echo 'Баланс: ' . $this->balance; } }

$acc = new PremiumAccount(); $acc->deposit(500); $acc->bonus(); $acc->show();

Вывод: ``` Баланс: 600 ```

Интерфейсы

Интерфейс — это «контракт», который обязует класс реализовать определённые методы. Он задаёт форму, но не реализацию.

Пример интерфейса


<?php
interface Logger {
  public function log($msg);
}

class FileLogger implements Logger { public function log($msg) { file_put_contents('log.txt', date('H:i:s') . ' — ' . $msg . "\n", FILE_APPEND); } }

$logger = new FileLogger(); $logger->log('Пользователь вошёл в систему.'); ?>

Файл log.txt будет содержать строки: ``` 12:01:45 — Пользователь вошёл в систему. ```

Зачем нужны интерфейсы

  • Они задают единый стандарт для всех классов, реализующих логику
  • Позволяют писать универсальный код (например, подменять логгер на EmailLogger, не меняя остальной код)

Несколько интерфейсов

Класс может реализовать несколько интерфейсов сразу:

interface A { public function testA(); }
interface B { public function testB(); }

class C implements A, B { public function testA() { echo 'A'; } public function testB() { echo 'B'; } }

$obj = new C(); $obj->testA(); $obj->testB();

Вывод: ``` AB ```

Абстрактные классы

Абстрактный класс — это некий «незаконченный» класс, который нельзя создать напрямую, но можно использовать как основу для других.

Пример


abstract class Shape {
  protected $color;

public function __construct($color) { $this->color = $color; }

abstract public function area(); // метод без реализации }

class Circle extends Shape { private $radius;

public function __construct($color, $radius) { parent::__construct($color); $this->radius = $radius; }

public function area() { return 3.14 * $this->radius ** 2; } }

$circle = new Circle('red', 5); echo 'Площадь круга: ' . $circle->area();

Вывод: ``` Площадь круга: 78.5 ```

Абстрактный класс vs интерфейс

  • Интерфейс — только описание методов, без реализации.
  • Абстрактный класс — может содержать и реализованные, и абстрактные методы.
  • Класс может наследовать только один абстрактный класс, но реализовать несколько интерфейсов.

Практика: мини-система логирования

Реализуем пример с двумя логгерами — один пишет в файл, другой выводит в консоль.

interface Logger {
  public function log($msg);
}

class FileLogger implements Logger { public function log($msg) { file_put_contents('file_log.txt', date('H:i:s') . ' ' . $msg . "\n", FILE_APPEND); } }

class ConsoleLogger implements Logger { public function log($msg) { echo '[ЛОГ] ' . $msg . '<br>'; } }

function process(Logger $logger) { $logger->log('Начало работы программы.'); $logger->log('Завершение работы программы.'); }

process(new FileLogger()); process(new ConsoleLogger());

Вывод: ``` [ЛОГ] Начало работы программы. [ЛОГ] Завершение работы программы. ```

Итоги урока

Теперь вы знаете:
  • Что такое наследование и как переопределять методы родителя
  • Как создавать интерфейсы и использовать их как контракты
  • Чем отличаются абстрактные классы от интерфейсов
  • Как использовать parent:: и protected свойства
  • Как писать гибкий и расширяемый код с ООП