Часть 2 — WordPress и объектно-ориентированное программирование: пример из реальной жизни
Опубликовано: 2021-07-29В нашем обзоре WordPress и объектно-ориентированного программирования мы рассмотрели теорию объектно-ориентированного программирования (ООП) и то, чего ожидать при его использовании.
Прежде чем мы перейдем к более конкретным примерам кодирования с использованием ООП, в этой статье мы попытаемся объяснить, как можно подойти к сценарию реального мира с другим мышлением, необходимым для ООП, и как это анализируется с использованием объектов и классов.
Сценарий из реальной жизни: отправка SMS
Это больше похоже на «прошлый» жизненный сценарий, так как в настоящее время SMS используется все меньше и меньше, но, как вы увидите, есть причина, по которой мы используем это в качестве примера!
Предположим, у вас есть мобильное устройство, и вы хотите отправить текстовое сообщение одному из ваших контактов. Сохраняя пример максимально простым, последовательность действий будет следующей:
- Подготовьте сообщение
- Выберите один из ваших контактов и добавьте в качестве получателя
- Отправить сообщение
Итак, давайте попробуем визуализировать шаги, которые вы должны выполнить, чтобы отправить свое сообщение:
Мы добавили несколько более подробных описаний действий, но более или менее все, что вы делаете, это 3 основных шага. Вы подготавливаете сообщение в редакторе устройства, выбираете получателя из своих контактов, а затем отправляете сообщение. Готово! Ваше сообщение отправлено.
Теперь, если бы мы представили в коде приложение, которое отправляет SMS-сообщение, мы должны проанализировать, по какому маршруту лучше следовать; процедурный или ООП подход.
Приложение с процедурным подходом
Если вы разработчик плагинов для WordPress, вы, скорее всего, знакомы с процедурным программированием .
Как мы описали ранее, процедурное программирование — это тип императивного программирования, в котором наши программы состоят из одной или нескольких процедур. Итак, как разработчик, вы разбиваете свой плагин на набор переменных , которые содержат ваши данные, и функций , которые работают с данными.
В нашем примере выше с SMS-сообщением вы должны выполнить ряд действий, которые приведут к желаемому результату. Как вы, возможно, уже догадались, у вас будет, например, переменная, содержащая текст сообщения, функция с параметром $contact
, которая возвращает номер телефона, и, наконец, функция, которая отправляет сообщение. В коде это будет выглядеть так:
function get_phone_number( $contact ) { // Code that finds the contact's number in the list of contacts return $phone_number; } function send_sms( $contact, $message ) { $phone_number = get_phone_number( $contact ); // Code that sends the message to this number print "Message Sent!"; }
И вы бы использовали его так:
$text = "Hello John"; function send_message( "John Doe", $text );
Итак, вы выполните ряд заданий, которые приведут вас к желаемому результату.
Конечно, в этом очень простом примере с ограниченными и очень специфическими требованиями вообще нет причин рассматривать использование ООП. Процедурного программирования более чем достаточно для достижения вашей цели. Но если вы подумаете о некоторых сценариях того, как это приложение может расширяться в будущем, вы можете понять, что в долгосрочной перспективе у вас могут возникнуть проблемы с масштабируемостью. Ниже мы постараемся объяснить, почему.
Расширение приложения с процедурным подходом
Допустим, вы хотите улучшить это приложение и предоставить возможность отправлять и другие виды сообщений, например, электронную почту. Функция, которая доставляет сообщение, будет отличаться в каждом случае.
При отправке электронного письма вам нужен адрес электронной почты контакта, а не номер телефона. Кроме того, нам нужно будет добавить в итоговую send_message()
параметр, который будет соответствовать типу используемой нами технологии; электронная почта или смс.
Соответствующий код может выглядеть примерно так:
function get_phone_number( $contact ) { // Code that finds the contact's number return $phone_number; } function get_email_address( $contact ) { // Code that finds the contact's email address return $email_address; } function send_sms( $contact, $message ) { $phone_number = get_phone_number( $contact ); // Code that sends the message to this number print "SMS Sent!"; } function send_email( $contact, $message ) { $email_address = get_email_address( $contact ); // Code that sends the email to this number print "Email Sent!"; } function send_message( $contact, $message, $technology ) { if ( $technology == "SMS") { send_sms( $phone_number, $message ); } else if ( $technology == "Email") { send_email( $email_address, $message ); } }
Таким образом, это не может быть реализовано с процедурным подходом. Но если вы опытный разработчик, вы, вероятно, уже поняли, как это может стать грязным в будущем.
Недостатки процедурного подхода
Что, если бы у нас было несколько типов сообщений? Операторы if
станут раздражающе большими. И, самое главное, что, если бы у вас были функции, использующие функцию send_message()
? В этом случае вам также потребуется добавить параметр $technology
во все эти функции.
По мере роста вашего кода функции будут появляться повсюду, а это означает, что вы начнете копировать/вставлять фрагменты кода (что нежелательно), а внесение небольшого изменения в функцию может привести к поломке нескольких других функций. Мы все были там. Вы хотели бы избежать этого и иметь возможность легко добавлять функции в свой код, не слишком вмешиваясь в структуру.
Объектно-ориентированное программирование (или ООП) — это парадигма программирования, которая пытается решить эту проблему, позволяя нам структурировать наш плагин в небольшие повторно используемые фрагменты кода, называемые классами . Как мы описали в нашей статье «Обзор ООП», класс — это, по сути, шаблон, который мы используем для создания отдельных экземпляров класса, называемых объектами .
Объект содержит данные и код. У нас все еще есть переменные, которые могут хранить информацию, называемые свойствами . А процедуры, которые оперируют данными, называются методами .
Приложение с ООП-подходом
Теперь давайте проанализируем тот же сценарий, что и выше, с подходом ООП.
Во-первых, мы определим, какие объекты у нас здесь есть, какие характеристики у каждого есть и какие действия они выполняют. Характеристики — это то, что позже станет нашими свойствами, а действия — нашими функциями или методами, как они называются в ООП.
Давайте подумаем, что мы имеем в первом сценарии отправки СМС самым простым способом. Есть устройство с интерфейсом, который мы используем для отправки SMS-сообщения. У нас есть содержимое сообщения, мы выбираем контакт в качестве получателя и, наконец, само сообщение.
<?php /** * Plugin Name: Send Message */ interface MessagingCapable { public function send_message( $contact, $message ); } class Phone implements MessagingCapable { public function send_message( $contact, $message ) { print "You sent" . $message ; } } function say_hi( MessagingCapable $device, $contact, $message ) { $device->send_message( $contact, $message ); }
Мы объявляем класс Phone
, реализующий интерфейс MessagingCapable
. Поэтому мы должны реализовать все объявленные в нем методы. Функция say_hi()
требует 3 параметра:
- Устройство, поддерживающее обмен сообщениями
- Контакт
- Сообщение
Чтобы на самом деле отправить сообщение, мы используем эту функцию следующим образом:
$phone = new Phone(); say_hi( $phone, "John Doe", "Hello John" );
По сути, мы создаем объект, создавая экземпляр класса Phone и передавая содержимое контакта и сообщения. Это выведет:
You sent "Hello John"
Мы продемонстрировали этот простой сценарий отправки текстового сообщения с помощью классов. В следующем разделе мы увидим, как мы можем расширить возможности приложения, следуя подходу ООП, и при масштабировании мы рассмотрим, где функции ООП играют свою роль, а также преимущества использования этого метода.
Расширение приложения с помощью ООП-подхода
Давайте также добавим возможность отправлять электронные письма, как мы делали это раньше процедурно.
Независимо от устройства, в идеале мы хотели бы использовать say_hi()
одинаково. Взгляните на код ниже:
<?php /** * Plugin Name: Send Message */ interface MessagingCapable { public function send_message( $contact, $message ); } class Phone implements MessagingCapable { public function send_message( $contact, $message ) { print ('You sent a "' . $message . '" SMS to ' . $contact ); } } class Computer implements MessagingCapable { public function send_message( $contact, $message ) { print ('You sent a "' . $message . '" email to ' . $contact ); } } function say_hi( MessagingCapable $device, $contact, $message ) { $device->send_message( $contact, $message ); }
Когда мы используем этот фрагмент кода, мы используем мобильное устройство для отправки SMS и компьютер для отправки электронного письма. Мы бы либо:
say_hi ( new Phone(), "John Doe", "Hello John" );
или же:
say_hi ( new Computer(), "John Doe", "Hello John" );
это выведет: You sent a "Hello John" SMS to John Doe
и You sent a "Hello John" email to John Doe
соответственно.
Здесь мы уже начинаем обнаруживать некоторые особенности ООП. Мы представили интерфейсы с помощью интерфейса MessagingCapable
.
Интерфейс объявляет набор методов, которые должны быть реализованы классом, не определяя, как эти методы реализованы. Все методы, объявленные в интерфейсе, должны быть общедоступными.
PHP не поддерживает множественное наследование, то есть класс не может наследовать свойства/методы нескольких родительских классов.
Хотя он может расширять только один класс, он может реализовывать несколько интерфейсов.
Использование телефона для отправки сообщения будет отличаться от использования компьютера. Экземпляры разных классов ведут себя по-разному, когда их просят выполнить одно и то же действие (например send_message()
). Это пример полиморфизма. Если позже мы создадим новое устройство, нам не нужно будет изменять наш код, чтобы приспособить его, если все они используют один и тот же интерфейс.
Мы также хотели бы отметить здесь, что мы уже видим разницу в удобочитаемости. То, как мы, наконец, используем этот скрипт, просто кодируя:
say_hi( new Computer(), "John", "Hi" );
Это совершенно просто для любого разработчика, который работает над проектом. И, конечно же, чем сложнее плагин, тем очевиднее его полезность, особенно при работе в команде.
Чтобы попытаться лучше объяснить, как легко расширить ваш плагин в объектно-ориентированном программировании, давайте попробуем добавить еще несколько функций.
Добавление дополнительных функций
Если мы хотим добавить возможность работы в Интернете, мы просто добавим дополнительный интерфейс для любого устройства, которое может реагировать на эту возможность, например, для компьютера.
interface InternetBrowsingCapable { public function visit_website( $url ); }
Реализация этого интерфейса будет закодирована следующим образом:
class Computer implements MessagingCapable, InternetBrowsingCapable { public function send_message( $contact, $message ) { print ('You sent a "' . $message . '" email to ' . $contact ); } public function visit_website( $url ) { print ('You visited "' . $url ); } }
Таким образом, в текущем классе Computer мы просто добавили дополнительный интерфейс, который необходимо реализовать, поскольку компьютер может отправлять сообщения и просматривать Интернет, а также метод visit_website( $url )
.
ПРИМЕЧАНИЕ. Конечно, поскольку посещение URL-адреса совершенно не имеет отношения к функции say_hi()
, мы также введем новую функцию, например:
function visit_url( InternetBrowsingCapable $device, $url ) { $device->visit_website( $url ); }
Вот и все! Для любого устройства, которое может посетить URL-адрес, мы можем использовать эту функцию, как мы это делали с компьютером. Не беспокойтесь, что вы сломаете остальную часть функциональности. Это показывает доступную масштабируемость при использовании ООП по сравнению с процедурным программированием.
Давайте добавим смартфон, чтобы продемонстрировать еще некоторые возможности. Вот весь код с добавлением класса смартфона, чтобы вы могли лучше понять, что происходит:
<?php /* * Plugin Name: Communication Plugin */ interface MessagingCapable { public function send_message( $contact, $message ); } interface InternetBrowsingCapable { public function visit_website( $url ); } class Phone implements MessagingCapable { public function send_message( $contact, $message ) { print 'You sent a "' . $message . '" SMS to ' . $contact; } } class Computer implements MessagingCapable, InternetBrowsingCapable { public function send_message( $contact, $message ) { print 'You sent a "' . $message . '" email to ' . $contact; } public function visit_website( $url ) { print 'You visited "' . $url; } } class Smartphone extends Phone implements InternetBrowsingCapable { public function visit_website( $url ) { print 'You visited "' . $url; } public function send_message( $contact, $message ) { parent::send_message( $contact, $message ); print ' from your smartphone'; } } function say_hi( MessagingCapable $device, $contact, $message ) { $device->send_message( $contact, $message ); } function visit_url( InternetBrowsingCapable $device, $url ) { $device->visit_website( $url ); }
Класс Smartphone расширяет родительский класс Phone и реализует интерфейс InternetBrowsingCapable
. Это означает, что он может отправить сообщение и посетить URL-адрес. Здесь мы обнаруживаем функцию наследования. Другими словами, у нас есть иерархия классов, родительский класс (телефон) и подкласс (смартфон).
Таким образом, объект Smartphone наследует все свойства и поведение родительского класса Phone. Таким образом, внутри дочернего класса мы можем добавить метод или переопределить метод родительского класса, как мы сделали с send_message()
в классе Smartphone. Мы сделали это, чтобы изменить вывод. Мы могли бы полностью проигнорировать этот метод и использовать send_message()
родительского класса как есть.
Вы можете попробовать код самостоятельно, вставив его в блок кода этого замечательного онлайн-инструмента PHP. Под кодом попробуйте любую из этих строк кода и посмотрите на разные результаты.
say_hi ( new Phone(), "John Doe", "Hello John" ); say_hi ( new Computer(), "John Doe", "Hello John" ); say_hi ( new Smartphone(), "John Doe", "Hello John" ); visit_url ( new Smartphone(), "https://www.pressidium.com" ); visit_url ( new Computer(), "https://www.pressidium.com" );
Для еще лучшего понимания всей концепции взгляните на диаграмму классов приведенного выше кода.
Как показано выше, при разработке отношений между классами мы не включаем общие элементы в дочерний класс. Кроме того, не забудьте обратить внимание на руководство слева, чтобы вы могли определить отношения и видимость их свойств и методов.
Если вы также хотите увидеть функцию инкапсуляции в действии, попробуйте включить класс Contact в любой из приведенных выше примеров сценариев, которые мы предоставили. Класс будет выглядеть так:
class Contact { private $name; private $phone_number; private $email_address; public function __construct( $name, $phone_number, $email_address ) { $this->name = $name; $this->phone_number = $phone_number; $this->email_address = $email_address; } public function get_name() { return $this->name; } public function get_phone_number() { return $this->phone_number; } public function get_email_address() { return $this->email_address; } }
Метод __construct()
автоматически вызывается при создании объекта. Теперь, когда мы создаем экземпляр класса Contact, вызывается его конструктор и устанавливает значения его частных свойств. Затем мы используем наши «геттеры», которые являются get_name()
, get_phone_number()
и get_email_address()
для получения этих значений.
Инкапсуляция связывает данные с методами, которые работают с данными, при этом ограничивая прямой доступ, предотвращая раскрытие скрытых деталей реализации.
Вывод
Надеюсь, эта статья помогла вам лучше понять объектно-ориентированное программирование. ООП действительно помогает упростить расширение приложения в будущем, если это необходимо, благодаря ясности и возможности повторного использования.
Кроме того, плагин, использующий ООП, будет быстрее и проще в исполнении. Это связано с тем, что методы, общие для всех объектов класса, потребляют память только один раз, при их объявлении.
Безопасность также повышается благодаря инкапсуляции. С другой стороны, в процедурном программировании все данные являются глобальными, что означает доступ к ним из любого места.
В результате вышеперечисленного обслуживание кода, производительность, масштабируемость и устранение неполадок также становятся намного проще для вас и вашей команды.
В следующих статьях этой серии мы увидим этот стиль программирования в действии, применив его к плагину WordPress. В частности, мы создадим копию плагина Limit Login Attempts версии 1.7.1, созданного Йоханом Энфельдтом, но максимально преобразованного с использованием объектно-ориентированного подхода.
В ходе этого процесса мы разберем поток плагинов и установим требования. В дальнейшем мы опробуем наши первые мысли о дизайне плагина, а на этапе реализации напишем код. В процессе реализации мы будем делать некоторые изменения и переделывать дизайн, если это необходимо, чтобы получить желаемые результаты.
Однако мы не будем вдаваться в подробности всех частей кода. Вместо этого мы хотели бы сосредоточиться на том, как плагины строятся объектно-ориентированным способом. Мы уверены, что после того, как вы закончите читать эту серию статей, вы вполне сможете создать собственный плагин ООП.
Нажмите здесь, чтобы прочитать часть 3 в нашей серии объектно-ориентированного программирования
Смотрите также
- WordPress и объектно-ориентированное программирование — обзор