Полиморфизм — одна из трех основных парадигм ООП. Если говорить кратко, полиморфизм — это способность обьекта использовать методы производного класса, который не существует на момент создания базового. Для тех, кто не особо сведущ в ООП, это, наверно, звучит сложно. Поэтому рассмотрим применение полиморфизма на примере.
Постановка задачи
Предположим, на сайте нужны три вида публикаций — новости, объявления и статьи. В чем-то они похожи — у всех них есть заголовок и текст, у новостей и объявлений есть дата. В чем-то они разные — у статей есть авторы, у новостей — источники, а у объявлений — дата, после которой оно становится не актуальным.
Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (get_news, get_announcements, get_articles) — это уже совсем неграмотно. Тут нам и поможет полиморфизм.
Абстрактный класс
Грубо говоря, это класс-шаблон. Он реализует функциональность только на том уровне, на котором она известна на данный момент. Производные же классы ее дополняют. Но, пора перейти от теории к практике. Сразу оговорюсь, рассматривается примитивный пример с минимальной функциональностью. Все объяснения — в комментариях в коде.
abstract class Publication
{
// таблица, в которой хранятся данные по элементу
protected $table;
// свойства элемента нам неизвестны
protected $properties = array();
// конструктор
public function __construct($id)
{
// обратите внимание, мы не знаем, из какой таблицы нам нужно получить данные
$result = mysql_query ('SELECT * FROM `'.$this->table.'` WHERE `id`="'.$id.'" LIMIT 1');
// какие мы получили данные, мы тоже не знаем
$this->properties = mysql_fetch_assoc($result);
}
// метод, одинаковый для любого типа публикаций, возвращает значение свойства
public function get_property($name)
{
if (isset($this->properties[$name]))
return $this->properties[$name];
return false;
}
// метод, одинаковый для любого типа публикаций, устанавливает значение свойства
public function set_property($name, $value)
{
if (!isset($this->properties[$name]))
return false;
$this->properties[$name] = $value;
return $value;
}
// а этот метод должен напечатать публикацию, но мы не знаем, как именно это сделать, и потому объявляем его абстрактным
abstract public function do_print();
}
Производные классы
Теперь можно перейти к созданию производных классов, которые и реализуют недостающую функциональность.
class News extends Publication
{
// конструктор класса новостей, производного от класса публикаций
public function __construct($id)
{
// устанавливаем значение таблицы, в которой хранятся данные по новостям
$this->table = 'news_table';
// вызываем конструктор родительского класса
parent::__construct($id);
}
// переопределяем абстрактный метод печати
public function do_print()
{
echo $this->properties['title'];
echo '<br /><br />';
echo $this->properties['text'];
echo '<br />Источник: '.$this->properties['source'];
}
}
class Announcement extends Publication
{
// конструктор класса объявлений, производного от класса публикаций
public function __construct($id)
{
// устанавливаем значение таблицы, в которой хранятся данные по объявлениям
$this->table = 'announcements_table';
// вызываем конструктор родительского класса
parent::__construct($id);
}
// переопределяем абстрактный метод печати
public function do_print()
{
echo $this->properties['title'];
echo '<br />Внимание! Объявление действительно до '.$this->properties['end_date'];
echo '<br /><br />'.$this->properties['text'];
}
}
class Article extends Publication
{
// конструктор класса статей, производного от класса публикаций
public function __construct($id)
{
// устанавливаем значение таблицы, в которой хранятся данные по статьям
$this->table = 'articles_table';
// вызываем конструктор родительского класса
parent::__construct($id);
}
// переопределяем абстрактный метод печати
public function do_print()
{
echo $this->properties['title'];
echo '<br /><br />';
echo $this->properties['text'];
echo '<br />© '.$this->properties['author'];
}
}
Теперь об использовании
Суть в том, что один и тот же код используется для обьектов разных классов.
// наполняем массив публикаций объектами, производными от Publication
$publications[] = new News($news_id);
$publications[] = new Announcement($announcement_id);
$publications[] = new Article($article_id);
foreach ($publications as $publication) {
// если мы работаем с наследниками Publication
if ($publication instanceof Publication) {
// то печатаем данные
$publication->do_print();
} else {
// исключение или обработка ошибки
}
}
Вот и все. Легким движением руки брюки превращаются в элегантные шорты :-).
Основная выгода полиморфизма — легкость, с которой можно создавать новые классы, «ведущие себя» аналогично родственным, что, в свою очередь, позволяет достигнуть расширяемости и модифицируемости. В статье показан всего лишь примитивный пример, но даже в нем видно, насколько использование абстракций может облегчить разработку. Мы можем работать с новостями точно так, как с объявлениями или статьями, при этом нам даже не обязательно знать, с чем именно мы работаем! В реальных, намного более сложных приложениях, эта выгода еще ощутимей.
Немного теории
- Методы, которые требуют переопределения, называются абстрактными. Логично, что если класс содержит хотя бы один абстрактный метод, то он тоже является абстрактным.
- Очевидно, что обьект абстрактного класса невозможно создать, иначе он не был бы абстрактным.
- Производный класс имеет свойства и методы, принадлежащие базовому классу, и, кроме того, может иметь собственные методы и свойства.
- Метод, переопределяемый в производном классе, называется виртуальным. В базовом абстрактном классе об этом методе нет никакой информации.
- Суть абстрагирования в том, чтобы определять метод в том месте, где есть наиболее полная информация о том, как он должен работать.
Оригинал на моем сайте