Личный сайт Андрея Сабинина

Сайт не обновляется с начала 2011 года, так как у меня на это нет ни времени, ни желания.

Заглавная / Архив блога о разработке / Разработка / Полиморфизм для начинающих
 
 

Природа возложила на каждую особь обязанность. Если обязанность не выполнена — особь умирает. Но выполнив ее, она все равно умирает. Природа равнодушна.

Джек Лондон.

 

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

sap, 14-го августа 2008 года в 23:08

Обращаю ваше внимание на то, что эта запись является архивной. Ей уже больше 4 лет. Это означает, во-первых, то, что мое мнение по поднятым в ней вопросам могло измениться (хотя, конечно, не обязательно), а во-вторых, то, что я не испытываю никакого желания эти вопросы обсуждать, и поэтому комментарии и оценки я отключил.


Что такое полиморфизм?

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

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

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

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

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

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

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

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

Евгений, 14-го августа 2008 года в 23:28

Самая лучшая статья про полиморфизм, к тому же с понятным примером :—).

vasa_c, 16-го сентября 2008 года в 13:57

Полиморфизм это несколько другое.

А наследование это одно из множества инструментов для достижения полиморфизма.

sap, 16-го сентября 2008 года в 15:22

Прошу не забывать, что это для начинающих. Наследование, в данном случае, самый понятный пример.

test1"><script>, 3-го ноября 2008 года в 16:14

</textarea>test

sap, 3-го ноября 2008 года в 22:41

Делать нефиг?)