В объектно-ориентированном программировании полиморфизм является мощным и фундаментальным инструментом. Его можно использовать для создания более органичного потока в вашем приложении. Этот урок опишет общую концепцию полиморфизма и как его можно легко развернуть в PHP.
Что такое полиморфизм?
Полиморфизм — это длинное слово для очень простого понятия.
Полиморфизм описывает шаблон в объектно-ориентированном программировании, в котором классы имеют различную функциональность, совместно используя общий интерфейс.
Прелесть полиморфизма в том, что коду, работающему с разными классами, не нужно знать, какой класс он использует, поскольку все они используются одинаково.
Реальная аналогия полиморфизма — это кнопка. Все знают, как использовать кнопку: вы просто нажимаете на нее. Однако то, что кнопка «делает», зависит от того, с чем она связана, и от контекста, в котором она используется, но результат не влияет на то, как она используется. Если ваш босс говорит вам нажать кнопку, у вас уже есть вся информация, необходимая для выполнения задачи.
В мире программирования полиморфизм используется, чтобы сделать приложения более модульными и расширяемыми. Вместо беспорядочных условных выражений, описывающих различные варианты действий, вы создаете взаимозаменяемые объекты, которые выбираете в зависимости от ваших потребностей. Это основная цель полиморфизма.
Интерфейсы
Неотъемлемой частью полиморфизма является общий интерфейс. Существует два способа определения интерфейса в PHP: интерфейсы и абстрактные классы . Оба имеют свои применения, и вы можете смешивать и сочетать их так, как считаете нужным в своей иерархии классов.
Интерфейс
Интерфейс похож на класс за исключением того, что он не может содержать код. Интерфейс может определять имена методов и аргументы, но не содержимое методов. Любые классы, реализующие интерфейс, должны реализовывать все методы, определенные интерфейсом. Класс может реализовывать несколько интерфейсов.
Интерфейс объявляется с использованием ключевого слова interface :
|
1
2
3
|
interface MyInterface {
// methods
}
|
и присоединяется к классу с помощью ключевого слова «Implements» (можно реализовать несколько интерфейсов, перечислив их через запятую):
|
1
2
3
|
class MyClass implements MyInterface {
// methods
}
|
Методы могут быть определены в интерфейсе так же, как в классе, кроме как без тела (часть между фигурными скобками):
|
1
2
3
4
5
|
interface MyInterface {
public function doThis();
public function doThat();
public function setName($name);
}
|
Все методы, определенные здесь, должны быть включены в любые реализующие классы точно так, как описано. (читайте код комментария ниже)
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// VALID
class MyClass implements MyInterface {
protected $name;
public function doThis() {
// code that does this
}
public function doThat() {
// code that does that
}
public function setName($name) {
$this->name = $name;
}
}
// INVALID
class MyClass implements MyInterface {
// missing doThis()!
private function doThat() {
// this should be public!
}
public function setName() {
// missing the name argument!
}
}
|
Абстрактный класс
Абстрактный класс — это смесь интерфейса и класса. Он может определять функциональность и интерфейс (в форме абстрактных методов). Классы, расширяющие абстрактный класс, должны реализовывать все абстрактные методы, определенные в абстрактном классе.
Абстрактный класс объявляется так же, как классы с добавлением ключевого слова abstract :
|
1
2
3
|
abstract class MyAbstract {
// methods
}
|
и присоединяется к классу с помощью ключевого слова extends:
|
1
2
3
|
class MyClass extends MyAbstract {
// class methods
}
|
Обычные методы могут быть определены в абстрактном классе, как и в обычном классе, а также в любых абстрактных методах (используя ключевое слово abstract ). Абстрактные методы ведут себя так же, как методы, определенные в интерфейсе, и должны быть реализованы в точности так, как это определено расширяющимися классами.
|
1
2
3
4
5
6
7
8
|
abstract class MyAbstract {
public $name;
public function doThis() {
// do this
}
abstract public function doThat();
abstract public function setName($name);
}
|
Шаг 1: выявить проблему
Давайте представим, что у вас есть класс Article который отвечает за управление статьями на вашем сайте. Он содержит информацию о статье, включая название, автора, дату и категорию. Вот как это выглядит:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
|
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;
}
}
|
Примечание . Примеры классов в этом руководстве используют соглашение об именах «package_component_Class». Это распространенный способ разделения классов на виртуальные пространства имен, чтобы избежать конфликтов имен.
Теперь вы хотите добавить метод для вывода информации в различных форматах, таких как XML и JSON. У вас может возникнуть соблазн сделать что-то вроде этого:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
class poly_base_Article {
//…
public function write($type) {
$ret = »;
switch($type) {
case ‘XML’:
$ret = ‘<article>’;
$ret .= ‘<title>’ .
$ret .= ‘<author>’ .
$ret .= ‘<date>’ .
$ret .= ‘<category>’ .
$ret .= ‘</article>’;
break;
case ‘JSON’:
$array = array(‘article’ => $obj);
$ret = json_encode($array);
break;
}
return $ret;
}
}
|
Это некрасивое решение, но оно работает — пока. Спросите себя, что произойдет в будущем, когда мы хотим добавить больше форматов? Вы можете продолжать редактировать класс, добавляя все больше и больше дел, но теперь вы только разбавляете свой класс.
Один важный принцип ООП состоит в том, что класс должен делать одну вещь, и он должен делать это хорошо.
Имея это в виду, условные выражения должны быть красным флажком, указывающим, что ваш класс пытается сделать слишком много разных вещей. Это где полиморфизм приходит.
В нашем примере ясно, что представлены две задачи: управление статьями и форматирование их данных. В этом руководстве мы преобразуем наш код форматирования в новый набор классов и узнаем, насколько просто использовать полиморфизм.
Шаг 2: Определите ваш интерфейс
Первое, что мы должны сделать, это определить интерфейс. Важно тщательно продумать ваш интерфейс, потому что любые изменения в нем могут потребовать внесения изменений в вызывающий код. В нашем примере мы будем использовать простой интерфейс для определения нашего единственного метода:
|
1
2
3
|
interface poly_writer_Writer {
public function write(poly_base_Article $obj);
}
|
Это так просто; мы определили публичный метод write() который принимает объект Article в качестве аргумента. Любые классы, реализующие интерфейс Writer, обязательно будут иметь этот метод.
Совет: Если вы хотите ограничить тип аргументов, которые могут быть переданы вашим функциям и методам, вы можете использовать подсказки типа, как мы это делали в методе write() ; он принимает только объекты типа poly_base_Article . К сожалению, подсказка возвращаемого типа не поддерживается в текущих версиях PHP, поэтому вы должны позаботиться о возвращаемых значениях.
Шаг 3: Создайте свою реализацию
С вашим определенным интерфейсом пришло время создавать классы, которые действительно делают вещи. В нашем примере у нас есть два формата, которые мы хотим вывести. Таким образом, у нас есть два класса Writer: XMLWriter и JSONWriter. Это зависит от них, чтобы извлечь данные из переданного объекта Article и отформатировать информацию.
Вот как выглядит XMLWriter:
|
01
02
03
04
05
06
07
08
09
10
11
|
class poly_writer_XMLWriter implements poly_writer_Writer {
public function write(poly_base_Article $obj) {
$ret = ‘<article>’;
$ret .= ‘<title>’ .
$ret .= ‘<author>’ .
$ret .= ‘<date>’ .
$ret .= ‘<category>’ .
$ret .= ‘</article>’;
return $ret;
}
}
|
Как вы можете видеть из объявления класса, мы используем ключевое слово Implements для реализации нашего интерфейса. Метод write() содержит функциональные возможности, специфичные для форматирования XML.
Теперь вот наш класс JSONWriter:
|
1
2
3
4
5
6
|
class poly_writer_JSONWriter implements poly_writer_Writer {
public function write(poly_base_Article $obj) {
$array = array(‘article’ => $obj);
return json_encode($array);
}
}
|
Весь наш код, специфичный для каждого формата, теперь содержится в отдельных классах. Каждый из этих классов несет исключительную ответственность за обработку определенного формата и ничего более. Никакой другой части вашего приложения не нужно заботиться о том, как они работают, чтобы использовать его, благодаря нашему интерфейсу.
Шаг 4. Используйте вашу реализацию
Определив наши новые классы, пришло время вернуться к нашему классу Article. Весь код, который жил в оригинальном методе write() , был включен в наш новый набор классов. Теперь наш метод должен использовать новые классы, например так:
|
1
2
3
4
5
6
|
class poly_base_Article {
//…
public function write(poly_writer_Writer $writer) {
return $writer->write($this);
}
}
|
Все, что сейчас делает этот метод, — это принимает объект класса Writer (то есть любой класс, реализующий интерфейс Writer), вызывает его метод write() , передавая себя ( $this ) в качестве аргумента, а затем перенаправляет возвращаемое значение прямо клиенту. код. Ему больше не нужно беспокоиться о деталях форматирования данных, и он может сосредоточиться на своей основной задаче.
Получение писателя
Вам может быть интересно, откуда вы берете объект Writer, поскольку вам нужно передать его этому методу. Это зависит от вас, и есть много стратегий. Например, вы можете использовать фабричный класс, чтобы получить данные запроса и создать объект:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class poly_base_Factory {
public static function getWriter() {
// grab request variable
$format = $_REQUEST[‘format’];
// construct our class name and check its existence
$class = ‘poly_writer_’ .
if(class_exists($class)) {
// return a new Writer object
return new $class();
}
// otherwise we fail
throw new Exception(‘Unsupported format’);
}
}
|
Как я уже сказал, есть много других стратегий для использования в зависимости от ваших требований. В этом примере переменная запроса выбирает, какой формат использовать. Он создает имя класса из переменной запроса, проверяет, существует ли оно, а затем возвращает новый объект Writer. Если под этим именем ничего не существует, выдается исключение, чтобы клиентский код выяснил, что делать.
Шаг 5: все вместе
Со всем на месте, вот как наш клиентский код соединил бы все это:
|
01
02
03
04
05
06
07
08
09
10
|
$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() нашей Статьи, печатая результат.
Вывод
В этом уроке я представил вам введение в полиморфизм и объяснение интерфейсов в PHP. Я надеюсь, вы понимаете, что я показал вам только один потенциальный вариант использования полиморфизма. Есть много, много других приложений. Полиморфизм — это элегантный способ избежать уродливых условных операторов в вашем ООП-коде. Он следует принципу разделения компонентов и является неотъемлемой частью многих шаблонов проектирования. Если у вас есть какие-либо вопросы, не стесняйтесь спрашивать в комментариях!