Pull to refresh

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

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

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


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

Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (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:
Hubs:
+50
Comments 130
Comments Comments 130

Articles