Все ленты — последние статьи

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

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


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

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

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

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

 

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

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

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

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

Интерфейс определяется ключевым словом ‘interface‘:
1 interface MyInterface {
2 // Методы
3 }

и присоединяется к классу с помощью ключевого слова ‘implements‘ (несколько интерфейсов могут быть использованы с помощью указания их один за другим через запятую):
1 class MyClass implements MyInterface {
2 // methods
3 }

Методы можно определять в интерфейсе также как и в классе, только без тела функции (части между фигурными скобками):
1 interface MyInterface {
2 public function doThis();
3 public function doThat();
4 public function setName($name);
5 }

Все методы, определенные в интерфейсе, должны быть реализованы в реализующем интерфейс классе. Причем методы обязательно должны быть публичными и в точности соответствовать определению в интерфейсе (смотри пример ниже)
01 // ПРАВИЛЬНО
02 class MyClass implements MyInterface {
03 protected $name;
04 public function doThis() {
05 // код метода
06 }
07 public function doThat() {
08 // код метода
09 }
10 public function setName($name) {
11 $this->name = $name;
12 }
13 }
14
15 // НЕПРАВИЛЬНО
16 class MyClass implements MyInterface {
17 // missing doThis()!
18
19 private function doThat() {
20 // метод обязательно должен быть публичным!
21 }
22 public function setName() {
23 // пропущен аргумент!
24 }
25 }
Абстрактный класс

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

Абстрактный класс определяется также, как и обычный класс, но с добавлением ключевого слова ‘abstract‘:
1 abstract class MyAbstract {
2 // Методы
3 }

и он присоединяется к классу с помощью ключевого слова ‘extends‘:
1 <div class="code_sample"><pre class="brush:php">class MyClass extends MyAbstract {
2 // Методы класса
3 }
4 </pre>
5 </div>

Обычные методы определяются в абстрактном классе также, как и в обычном классе, а абстрактные методы определяются с использованием ключевого слова ‘abstract‘. Абстрактные методы используются также как в интерфейсе и должны быть реализованы в расширяющем классе.
1 abstract class MyAbstract {
2 public $name;
3 public function doThis() {
4 // код функции
5 }
6 abstract public function doThat();
7 abstract public function setName($name);
8 }
Шаг 1: Описание проблемы

Допустим у нас есть класс Article, который управляет статьями на сайте. Он содержит информацию о статье, включая заголовок, автора, дату создания и категорию. Класс выглядит примерно так::
01 class poly_base_Article {
02 public $title;
03 public $author;
04 public $date;
05 public $category;
06
07 public function __construct($title, $author, $date, $category = 0) {
08 $this->title = $title;
09 $this->author = $author;
10 $this->date = $date;
11 $this->category = $category;
12 }
13 }

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

Теперь надо добавить методы для вывода информации в разных форматах, таких как XML и JSON. Есть очень большой соблазн сделать вот так:
01 class poly_base_Article {
02 //…
03 public function write($type) {
04 $ret = '';
05 switch($type) {
06 case 'XML':
07 $ret = '<article>';
08 $ret .= '<title>' . $obj->title . '</title>';
09 $ret .= '<author>' . $obj->author . '</author>';
10 $ret .= '<date>' . $obj->date . '</date>';
11 $ret .= '<category>' . $obj->category . '</category>';
12 $ret .= '</article>';
13 break;
14 case 'JSON':
15 $array = array('article' => $obj);
16 $ret = json_encode($array);
17 break;
18 }
19 return $ret;
20 }
21 }

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

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

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

Первым делом надо определить интерфейс. Очень важно хорошо продумать структуру интерфейса, потому что любые изменения кода интерфейса потребуют изменений всех классов, где он будет использоваться. В нашем примере мы будем использовать простой интерфейс для определения одного метода:
1 interface poly_writer_Writer {
2 public function write(poly_base_Article $obj);
3 }

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

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

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

Вот код XMLWriter:
01 class poly_writer_XMLWriter implements poly_writer_Writer {
02 public function write(poly_base_Article $obj) {
03 $ret = '<article>';
04 $ret .= '<title>' . $obj->title . '</title>';
05 $ret .= '<author>' . $obj->author . '</author>';
06 $ret .= '<date>' . $obj->date . '</date>';
07 $ret .= '<category>' . $obj->category . '</category>';
08 $ret .= '</article>';
09 return $ret;
10 }
11 }

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

А вот код класса JSONWriter:
1 class poly_writer_JSONWriter implements poly_writer_Writer {
2 public function write(poly_base_Article $obj) {
3 $array = array('article' => $obj);
4 return json_encode($array);
5 }
6 }

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

Теперь имея определения новых классов надо провести ревизию класса статьи. Весь код, который был помещен в оригинальный метод write(), был реализован в наших новых классах. Все методы должны теперь использовать новые классы:
1 class poly_base_Article {
2 //…
3 public function write(poly_writer_Writer $writer) {
4 return $writer->write($this);
5 }
6 }

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

Но как получить объект Writer, который будет выполнять данный метод? Все зависит от вас, и существует много различных стратегий. Например, вы можете создать класс фабрику для перехвата данных запроса и создания объекта:
01 class poly_base_Factory {
02 public static function getWriter() {
03 // перехватываем переменную запроса
04 $format = $_REQUEST['format'];
05 // конструируем имя нашего класса и проверяем его существование
06 $class = 'poly_writer_' . $format . 'Writer';
07 if(class_exists($class)) {
08 // возвращаем новый объект Writer
09 return new $class();
10 }
11 // иначе выдаем сообщение об ошибке
12 throw new Exception('Не поддерживаемый формат');
13 }
14 }

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

Код, который соединяет функциональность в единое целое может выглядеть примерно так:
01 $article = new poly_base_Article('Polymorphism', 'Steve', time(), 0);
02
03 try {
04 $writer = poly_base_Factory::getWriter();
05 }
06 catch (Exception $e) {
07 $writer = new poly_writer_XMLWriter();
08 }
09
10 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/
Перевел: Сергей Фастунов