Полиморфизм в PHP

В обьектно-ориентированном программировании полиморфизм является мощным и фундаментальным инструментом. Он может быть использован для создания более органичной структуры приложения. Данный урок описывает общее понятие полиморфизма и его приложение к PHP.

 

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

Полиморфизм - длинное слово для очень простой концепции.

Полиморфизм описывает шаблон в объектно ориентированном программировании, в котором классы имеют различную функциональность при использовании общего интерфейса.

Прелесть полиморфизма заключается в том, что можно работать в коде с различными классами, и при этом не нужно знать, что за класс используется, потому что они имеют один и тот же интерфейс.

Аналогия полиморфизма в реальном мире - кнопка. Каждый знает как использовать кнопку - нужно просто нажать на нее. Но то, что "делает" кнопка в действительности, зависит от ее соединений и контекста использования. Если кто-то говорит, что нужно нажать кнопку, то уже известно, что нужно сделать, чтобы решить задачу.

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

Интерфейсы

Интегральная часть полиморфизма - общий интерфейс. Существует два способа определить интерфейс в PHP: интерфейс и абстрактный класс. Оба способа имеют свое назначение, их можно использовать совместно или выбирать тот, который лучше подходит к иерархии классов.

Интерфейс

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

Интерфейс определяется ключевым словом ‘interface‘:

interface MyInterface {
    // Методы
}

и присоединяется к классу с помощью ключевого слова ‘implements‘ (несколько интерфейсов могут быть использованы с помощью указания их один за другим через запятую):

class MyClass implements MyInterface {
    // methods
}

Методы можно определять в интерфейсе также как и в классе, только без тела функции (части между фигурными скобками):

interface MyInterface {
    public function doThis();
    public function doThat();
    public function setName($name);
}

Все методы, определенные в интерфейсе, должны быть реализованы в реализующем интерфейс классе. Причем методы обязательно должны быть публичными и в точности соответствовать определению в интерфейсе (смотри пример ниже)

// ПРАВИЛЬНО
class MyClass implements MyInterface {
    protected $name;
    public function doThis() {
        // код метода
    }
    public function doThat() {
        // код метода
    }
    public function setName($name) {
        $this->name = $name;
    }
}

// НЕПРАВИЛЬНО
class MyClass implements MyInterface {
    // missing doThis()!

    private function doThat() {
        // метод обязательно должен быть публичным!
    }
    public function setName() {
        // пропущен аргумент!
    }
}

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

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

Абстрактный класс определяется также, как и обычный класс, но с добавлением ключевого слова ‘abstract‘:

abstract class MyAbstract {
    // Методы
}

и он присоединяется к классу с помощью ключевого слова ‘extends‘:

class MyClass extends MyAbstract {
    // Методы класса
}

Обычные методы определяются в абстрактном классе также, как и в обычном классе, а абстрактные методы определяются с использованием ключевого слова ‘abstract‘. Абстрактные методы используются также как в интерфейсе и должны быть реализованы в расширяющем классе.

abstract class MyAbstract {
    public $name;
    public function doThis() {
        // код функции
    }
    abstract public function doThat();
    abstract public function setName($name);
}

Шаг 1: Описание проблемы

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

class poly_base_Article {
    public $title;
    public $author;
    public $date;
    public $category;

    public function  __construct($title, $author, $date, $category = 0) {
        $this->title = $title;
        $this->author = $author;
        $this->date = $date;
        $this->category = $category;
    }
}

Примечание: Примеры классов в данном уроке будут использовать соглашение о наименовании “пакет_компонент_Класс”. Таким образом будут разделяться классы в виртуальном пространстве имен, чтобы избежать коллизий.

Теперь надо добавить методы для вывода информации в разных форматах, таких как XML и JSON. Есть очень большой соблазн сделать вот так:

class poly_base_Article {
    //...
    public function write($type) {
        $ret = '';
        switch($type) {
            case 'XML':
                $ret = '<article>';
                $ret .= '<title>' . $obj->title . '</title>';
                $ret .= '<author>' . $obj->author . '</author>';
                $ret .= '<date>' . $obj->date . '</date>';
                $ret .= '<category>' . $obj->category . '</category>';
                $ret .= '</article>';
                break;
            case 'JSON':
                $array = array('article' => $obj);
                $ret = json_encode($array);
                break;
        }
        return $ret;
    }
}

Но такое решение является убогим, хотя оно и будет работать в текущий момент. Спросите себя, что произойдет в будущем, когда нужно будет добавить еще какой-нибудь формат вывода? Вы будете редактировать класс, добавляя все больше и больше выражений  case, и таким образом утяжелите код класса.

Один из принципов объектно ориентированного программирования гласит, что класс должен делать единственную операцию, но он должен делать ее очень качественно.

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

Шаг 2: Определяем интерфейс

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

interface poly_writer_Writer {
    public function write(poly_base_Article $obj);
}

Все очень просто. Мы определили публичный метод write(), который принимает в качестве аргумента объект статьи. Любой класс, который реализует наш интерфейс определенно должен иметь метод вывода.

Совет: Если вы хотите ограничить тип аргумента, который будет передаваться в ваши функции и методы, вы можете использовать явное указание типа аргумента, как это сделано в методе write().  Он принимает только объекты типа poly_base_Article. К сожалению, явно указать тип возвращаемого значения в текущей версии PHP нельзя, поэтому нужно быть очень внимательным с возвращаемыми значениями.

Шаг 3: Создаем реализацию

После определения интерфейса нужно создать класс, который будет выполнять настоящие действия. В нашем примере у нас есть два формата, которые используются для вывода содержания статьи. Таким образом мы имеем два класса: XMLWriter и JSONWriter. Они извлекают данные из переданной статьи и форматируют информацию.

Вот код XMLWriter:

class poly_writer_XMLWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $ret = '<article>';
        $ret .= '<title>' . $obj->title . '</title>';
        $ret .= '<author>' . $obj->author . '</author>';
        $ret .= '<date>' . $obj->date . '</date>';
        $ret .= '<category>' . $obj->category . '</category>';
        $ret .= '</article>';
        return $ret;
    }
}

В определении класса используется ключевое слово  implements для реализации нашего интерфейса. Метод write() содержит код преобразования в XML.

А вот код класса JSONWriter:

class poly_writer_JSONWriter implements poly_writer_Writer {
    public function write(poly_base_Article $obj) {
        $array = array('article' => $obj);
        return json_encode($array);
    }
}

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

Шаг 4: Используем наши реализации

Теперь имея определения новых классов надо провести ревизию класса статьи. Весь код, который был помещен в оригинальный метод  write(),  был реализован в наших новых классах. Все методы должны теперь использовать новые классы:

class poly_base_Article {
    //...
    public function write(poly_writer_Writer $writer) {
        return $writer->write($this);
    }
}

Все методы теперь используются через класс Writer (любой класс, который реализует интерфейс Writer), с помощью вызова метода write(), с переданным ему $this в качестве аргумента, а возвращаемое значение используется прямо в коде. Больше не нужно беспокоиться о форматировании данных и можно сконцентрироваться на основной задаче.

Получаем объект Writer

Но как получить объект Writer, который будет выполнять данный метод? Все зависит от вас, и существует много различных стратегий. Например, вы можете создать класс фабрику для перехвата данных запроса  и создания объекта:

class poly_base_Factory {
    public static function getWriter() {
        // перехватываем переменную запроса
        $format = $_REQUEST['format'];
        // конструируем имя нашего класса и проверяем его существование
        $class = 'poly_writer_' . $format . 'Writer';
        if(class_exists($class)) {
            // возвращаем новый объект Writer
            return new $class();
        }
        // иначе выдаем сообщение об ошибке
        throw new Exception('Не поддерживаемый формат');
    }
}

В данном примере переменная запроса выбирает формат для использования. Мы конструируем имя класса, проверяем его существование и возвращаем новый объект Writer. А если сконструированного имени не существует, то генерируется исключение, чтобы код клиента мог корректно обработать ситуацию.

Шаг 5: Соединяем все вместе

Код, который соединяет функциональность в единое целое может выглядеть примерно так:

$article = new poly_base_Article('Polymorphism', 'Steve', time(), 0);

try {
    $writer = poly_base_Factory::getWriter();
}
catch (Exception $e) {
    $writer = new poly_writer_XMLWriter();
}

echo $article->write($writer);

Сначала создается объект Article. Затем мы пытаемся получить объект Writer с помощью Factory, если генерируется исключение то используется формат по умолчанию (XMLWriter). В завершении мы передаем объект Writer методу write() нашего объекта Article для вывода результата.

Заключение

Данный урок является введением в полиморфизм и описание интерфейсов в PHP. В примере был показан один из возможных способов потенциального использования полиморфизма. Он является элегантным способом избежать использования громоздких условных выражений в объектно ориентированном коде. полиморфизм помогает следовать принципу разделения компонентов и является интегральной частью многих шаблонов программирования.

Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: net.tutsplus.com/tutorials/php/understanding-and-applying-polymorphism-in-php/
Перевел: Сергей Фастунов
Урок создан: 20 Сентября 2010
Просмотров: 50813
Правила перепечатки


5 последних уроков рубрики "PHP"

  • Фильтрация данных с помощью zend-filter

    Когда речь идёт о безопасности веб-сайта, то фраза "фильтруйте всё, экранируйте всё" всегда будет актуальна. Сегодня поговорим о фильтрации данных.

  • Контекстное экранирование с помощью zend-escaper

    Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.

  • Подключение Zend модулей к Expressive

    Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.

  • Совет: отправка информации в Google Analytics через API

    Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.

  • Подборка PHP песочниц

    Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.

^ Наверх ^