20 August 2008

Полиморфизм для начинающих

PHP
Полиморфизм — одна из трех основных парадигм ООП. Если говорить кратко, полиморфизм — это способность обьекта использовать методы производного класса, который не существует на момент создания базового. Для тех, кто не особо сведущ в ООП, это, наверно, звучит сложно. Поэтому рассмотрим применение полиморфизма на примере.

Постановка задачи


Предположим, на сайте нужны три вида публикаций — новости, объявления и статьи. В чем-то они похожи — у всех них есть заголовок и текст, у новостей и объявлений есть дата. В чем-то они разные — у статей есть авторы, у новостей — источники, а у объявлений — дата, после которой оно становится не актуальным.

Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (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 />&copy; '.$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 {
// исключение или обработка ошибки
}
}


Вот и все. Легким движением руки брюки превращаются в элегантные шорты :-).

Основная выгода полиморфизма — легкость, с которой можно создавать новые классы, «ведущие себя» аналогично родственным, что, в свою очередь, позволяет достигнуть расширяемости и модифицируемости. В статье показан всего лишь примитивный пример, но даже в нем видно, насколько использование абстракций может облегчить разработку. Мы можем работать с новостями точно так, как с объявлениями или статьями, при этом нам даже не обязательно знать, с чем именно мы работаем! В реальных, намного более сложных приложениях, эта выгода еще ощутимей.

Немного теории

  • Методы, которые требуют переопределения, называются абстрактными. Логично, что если класс содержит хотя бы один абстрактный метод, то он тоже является абстрактным.
  • Очевидно, что обьект абстрактного класса невозможно создать, иначе он не был бы абстрактным.
  • Производный класс имеет свойства и методы, принадлежащие базовому классу, и, кроме того, может иметь собственные методы и свойства.
  • Метод, переопределяемый в производном классе, называется виртуальным. В базовом абстрактном классе об этом методе нет никакой информации.
  • Суть абстрагирования в том, чтобы определять метод в том месте, где есть наиболее полная информация о том, как он должен работать.

UPD: по поводу sql-inj и нарушения MVC — господа, это просто пример, причем пример по полиморфизму, в котором я не считаю нужным уделять значения этим вещам. Это тема для совсем других статей.

Оригинал у меня на сайте
Tags:phpоопполиморфизмабстрагирование
Hubs: PHP
+50
637.1k 294
Comments 130
Popular right now