Глава 9
Объектно-ориентированное
программирование на РНР
В этой главе...
С самого начала РНР рассматривался как простой язык написания сценариев. Позже, начиная с версии 4, в нем появились объектно-ориентированные свойства. После появления
версии 5 возможности объектно-ориентированного программирования на языке РНР были существенно расширены, а кроме того, повысилось быстродействие разрабатываемых программ. Однако большинство из новых свойств остаются невидимыми. Эти изменения в основном касаются ядра Zend 2, которое в РНР 5 стало гораздо эффективнее по сравнению с ядром РНР 4. Кгоме
повышения скорости обработки сценариев, в РНР 5 были добавлены новые возможности, которые сделали этот язык программирования по-настоящему объектно-ориентированным.
Введение в объектно-ориентированное программирование к оглавлению
Объектно-ориентированный подход к программированию предполагает использование объектов и классов. Более подробно они рассматриваются в этой главе ниже. В настоящее время
объектно-ориентированный подход широко распространен, и многие университетские курсы по
программированию начинаются именно с его изучения. При объектно-ориентированном программировании чаще всего используются языки программирования Java и C++.
Объектно-ориентированное программирование— это не просто использование другого
синтаксиса, а во многом и другой подход к анализу и решению задач. Объектно-ориентированная программа разрабатывается путем моделирования процессов в реальной
предметной области. Например, программист, разрабатывающий программу поддержкки
функционирования отдела продаж некоторой компании, может рассматривать ее с точки зрения взаимодействия покупателей, продавцов и кредитных политик, т.е. в терминах, которыми
оперируют специалисты самого отдела продаж.
В объектно-ориентированном программировании основными элементами программы являются объекты (object). Они являются представлением реальных объектов, которые задействованы при решении реальной проблемы. Например, если приложение относится к предметной области продажи подержанных автомобилей, то, возможно, в качестве объектов
будут выбраны автомобили и покупатели. Если же решаемая задача связана с космосом, то
объекты будут представлять звезды и планеты.
Объектно-ориентированное программирование является новой концепцией, которая предоставляет новую терминологию для описания рассматриваемых проблем. Понимание этой
терминологии — ключ к пониманию самого объектно-ориентированного подхода.
Объекты и классы
Основными элементами объектно-ориентированных программ являются объекты (object).
Лучше всего их представлять как физические объекты из реального мира. Например, автомобиль является объектом с набором свойств (или атрибутов), таких как цвет, название модели,
двигатель и тип шин. Кроме того, автомобиль может также выполнять некоторые действия,
например перемещаться вперед и назад, парковаться, поворачивать и останавливаться.
В общем случае объекты соответствуют именам существительным. Человек является объектом, так же как и животные, дома, офисы, покупатели, мусорные баки, пальто, облака, планеты и кнопки. Однако объекты — это не просто физические объекты. Как и имена существительные, зачастую объекты являются более концептуальными. Например, банковский счет
невозможно подержать в руках, но он тем не менее является объектом. То же самое можно
сказать и про компьютерную учетную запись, залог, файл, базу данных, заказы, сообщения
электронной почты, адреса, песни, телевизионные шоу, встречи, даты и т.д.
В свою очередь, класс (class) является шаблоном (каркасом), который можно использовать
для создания объектов. Класс определяет свойства и атрибуты объекта, а также действия, которые он может выполнять. Например, рассмотрим класс, определяющий четырехколесный автомобиль с двигателем, который может перемещаться вперед и парковаться. На его основе можно
создать новый объект (новый автомобиль). Однако в процессе использования этого объекта можно
обнаружить отсутствие некоторых важных деталей, таких как дверь, руль или задняя передача. Это
может произойти из-за того, что эти компоненты при создании класса не были учтены.
Разработчик, занимающийся созданием класса, знает все о его внутреннем устройстве.
Однако для тех, кто будет использовать этот класс, подобная информация вовсе необязательна. Например, необязательно знать, как функционирует телефон, — позвонить можно и без
этой информации. Специалисту, сконструировавшему телефон, известно о его внутренней
архитектуре. Поэтому при появлении новых технологий он сможет вскрыть телефонный аппарат и усовершенствовать его. Вместе с тем, до тех пор, пока не изменится внешний интерфейс (клавишная панель и кнопки) телефона, на его использование ничто не повлияет.
Свойства
Объекты имеют свойства (property), также называемые атрибутами (attribute). Автомобиль
может быть красным, зеленым или быть покрашенным в горошек. Такие свойства автомобиля,
как цвет, размер или модель, являются внутренними характеристиками объекта. Обычно свойства класса задаются в виде переменных. Например, атрибут цвета может храниться в объекте
в виде переменной с отличительным именем, например $color. Тогда в объекте "автомобиль"
может содержаться переменная $color со значением красный ($color = красный).
Значения переменных, являющихся свойствами объектов, могут присваиваться по умолчанию или явно во время создания объектов. Они могут добавляться и изменяться и позже.
Например, первоначально автомобиль может быть красным, а затем стать бледно-зеленым.
Методы
Действия, которые могут выполнять объекты, иногда рассматриваются как его обязанности (responsibility). Например, объект "автомобиль" может перемещаться вперед и назад, останавливаться и парковаться. Каждое действие (обязанность), которое объект может выполнять, реализуется в классе в виде метода (method)
В языке PHP метод имеет тот же синтаксис, что и функция. Разница заключается в том,
что методы находятся внутри класса. Они не могут быть вызваны независимо от класса —
интерпретатор РНР не позволит сделать это. Функции такого вида могут использоваться
только в контексте объекта.
Как правило, при создании методов им присваивают имена, соответствующие выполняемым действиям, например parkCar () или getColor (). Методы могут иметь любые допустимые имена, однако в соответствии с общепринятыми соглашениями в их именач используются прописные символы.
Методы представляют собой интерфейс между объектом и остальным миром. Каждому
действию (обязанности) объекта должен соответствовать свой метод. С объектом можно
взаимодействовать лишь через его интерфейс. Например, если ваш сосед хочет одолжить
у вас немного сахара, ему следует сначала постучать в вашу дверь и попросить об этой услуге. Вряд ли вы будете довольны, если сосед залезет в вашу кухню через окно и возьмет его
сам. Чтобы этого не случилось, ваш дом (house) (как объект) должен иметь входную дверь
(front door). И только через нее сосед (neighbor) будет иметь возможность попасть
в дом. Другими словами, в объекте house должен содержаться метод открытьВходнуюДверь
(openFrontDoor), который и необходимо использовать соседу. При этом другие варианты
проникновения в дом должны отсутствовать. Открытие входной двери front cloor
(действие объекта house) должно осуществляться с помощью метода openDoor (). При
создании объектов не оставляйте в них "открытых окон".
Хорошо спроектированный объект должен содержать всё необходимое для выполнения своих обязанностей, но при этом не иметь ничего лишнего. Он не должен выполнять
обязанности других объектов. Например, объект "автомобиль" должен "уметь" перeмещаться и иметь все компоненты, необходимые для выполнения своих обязанностей, например бензин, масло, шины, двигатель и т.д. Он не должен иметь соль и сковородку,
а также "уметь" готовить пищу. В свою очередь, объект для приготовления пищи не должен обучать детей игре в футбол.
Наследование
Объекты должны содержать только необходимые свойства и методы. Одним из подходов
для разделения свойств и методов между классами является наследование (inheritance). Пусть,
например, имеются два объекта, один из которых соответствует розам белого цвета, а другой — красного. Можно создать два класса: redRose (красная роза) и whiteRose (белая роза). Однако большая часть информации будет одинаковой для обоих классов, поскольку
оба вида роз относятся к кустарникам, имеют колючки и цветут в июне. Наследование позволяет избежать дублирования информации.
Сначала можно создать один класс Rose. Он может содержать общую информацию для
роз, такую как $plant = bush (вид растения — кустарник), $stem = thorns (стебел.—
с колючками), $blooms = June (цветет в июне). Затем можно создать два подкласса для
соответствующих видов роз. Тогда класс Rose будет являться главным классом (master class),
или родительским классом (parent class). В свою очередь, классы redRose и whiteRose называются подклассами (subclass), или наследниками (child class), или потомками (child).
Классы-потомки наследуют все атрибуты и методы родительского класса. Однако они могут иметь и свои собственные свойства, например белый цвет ($color = белый) для класса
whiteRose и красный цвет ($color = красный) — для класса redRose.
Методы, содержащиеся в классе-наследнике, могут иметь те же имена, что и методы родительского класса. В этом случае метод класса-потомка имеет больший приоритет. При необходимости можно воспользоваться любым из этих методов.
Что отсутствует в объектно-ориентированной парадигме РНР 5
Если вы знакомы с объектно-ориентированным программированием на других языках, то,
возможно, обнаружите, что некоторые из его характерных свойств в языке РНР отсутствуют.
Однако жизнь не стоит на месте, и многие возможности, которых не было в РНР 4, были добавлены в РНР 5. Однако по-прежнему отсутствуют следующие механизмы.
| |
|
|
Полиморфизм (polymorphism). В сценарии РНР в одном классе не может
быть двух методов с одинаковыми именами, даже конструкторов. Другими
словами, в РНР нельзя реализовать полиморфизм. В одном классе невозможно иметь два метода с одним и тем же именем, но с разным набором параметров (сигнатурами). Для реализации полиморфизма программисты иногда используют операторы switch и другие механизмы. |
Множественное наследование (multiple inheritance). В языке РНР отсутствует механизм множественного наследования. Класс может быть наследником
только одного родительского класса. |
Разработка объектно-ориентированных программ
Объектно-ориентированные программы требуют тщательного анализа и планирования гораздо больше, чем процедурные программы, в которых операторы обрабатываются от начала
до конца и не используются классы. Необходимо тщательно спланировать, какие свойства
и методы будут содержать объекты. При этом должны быть учтены все важные аспекты
функционирования предметной области. Однако обязанности классов не должны быть раздуты. При реализации сложных проектов может понадобиться создать модель и выполнить тестирование. Лишь после этого можно быть уверенным в том, что в состав программной системы входят все необходимые объекты.
Выбор объектов
При разработке программы сначала нужно сформулировать перечень требуемых объектов. Если приложение не очень большое, то выполнить это достаточно просто. Но если программная система является большой и сложной, то список требуемых объектов может оказаться
гораздо менее очевидным. Например, если разрабатывается программное обеспечение для банка, то можно выбрать такие объекты, как счет, кассир, деньги, чековая книжка, корзина для бумаги, служба охраны, сейф, система безопасности, клиент, ссуда, кредитная ставка и т.д. Однако
все ли эти объекты действительно необходимы? Что программа должна делать с корзиной для
бумаги в холле или со службой охраны? Возможно, понадобится составлять график ее работы.
Один из возможных подходов к идентификации объектов заключается в следующем. Сначала сформируйте перечень всех возможных объектов (т.е. имен существительных) из предметной
области. Для этого разработчики иногда могут воспользоваться проектной документацией.
Затем из сформированного списка следует вычеркнуть как можно больше объектов. Необходимо исключить повторяющиеся объекты, объекты с повторяющимися обязанностями
или те из них, которые напрямую не относятся к исследуемой предметной области. Например, если проект связан с конструированием автомобилей, в качестве объектов наверняка
пригодятся все его детали. С другой стороны, если проект связан с управлением движением
автомобилей, то их составные части окажутся гораздо менее важными.
Выбор свойств и методов для каждого объекта
После формирования списка объектов можно приступить к выбору свойств и методов для
каждого из них. Спросите себя, что вам нужно знать о каждом объекте. Например, в программе, связанной с ремонтом автомобилей, пригодится информация о том, когда автомобиль последний раз проходил технический осмотр и был в ремонте, каковы характеристики
его узлов и агрегатов, и т.д.
Объекты должны быть независимыми, для каждого из них необходимо определить обязанности и методы, которые они будут выполнять. В качестве примера рассмотрим объект
"банковский счет". Сначала его нужно создать (т.е. открыть). Для этого подойдет метод
openBankAccount (). После этого с банковским счетом можно осуществлять различные
операции: класть деньги на счет, оплачивать счета, следить за балансом счета и выдавать
о нем различную информацию.
Однако после некоторых размышлений и экспериментов может оказаться, что все-таки
что-то было упущено. Например, со счетом должны быть связаны данные о владельце его
имени и об адресе. Не забыли ли вы добавить метод обновления этой информации, если владелец переедет на новое место жительства?
Создание и использование класса
После выявления объектов и их свойств можно приступать к их созданию и использованию. Для создания и использования объекта нужно выполнить следующие действия.
- Использовать ключевое слово class. Ключевое слово class является основным при
создании класса. С ним связывается блок инструкций со свойствами и методами класса.
- Вставить выражение class в те места, где необходимо использовать соответствующий объект. Класс можно разместить в самом сценарии. Однако чаще всего конструкцию class размещают в отдельном файле, который включается в сценарий
с помощью директивы include
- Создать объект в сценарии. На основе описания класса можно создавать объекты,
т.е. выполнять инстанцирование (instantiation).
- Использовать объект. После создания нового объекта можно приступать к его использованию. При этом можно использовать любые методы класса, определенные
в соответствующем блоке при его описании.
Оставшаяся часть этой главы будет посвящена более подробному описанию каждого из
этих этапов.
Определение класса
После выявления требуемых объектов, их свойств и методов можно приступать к определению соответствующих классов. Класс является шаблоном (каркасом) для создания объектов.
Выражение class используется для определения свойств и методов класса и имеет следующий синтаксис:
class имяКласса
{
Определение свойств класса
Определение методов класса
}
В качестве имени класса можно использовать любой корректный идентификатор РНР,
кроме stdClass — зарезервированного служебного имени РНР.
Описание свойств и методов класса заключается в фигурные скобки. Если необходимо, чтобы класс наследовал свойства и методы другого класса, следует воспользоваться выражением
class whiteRose extends Rose
{
Определение свойств класса
Определение методов класса
}
Объект, созданный на основе этого класса, будет иметь доступ ко всем свойствам и методам как класса whiteRose, так и Rose. Однако класс Rose не имеет доступа к свойствам
и методам класса-наследника.
Следующие несколько разделов посвящены вопросам, связанным с определением свойств
и методов класса. Более исчерпывающий пример создания класса приведен ниже, в разделе
"Собирая все вместе".
Определение свойств
При создании класса все его свойства следует объявить в начале соответствующего блока.
class Car
{
var $color;
var $tires;
var $gas;
Методы
}
В общем случае в РНР объявлять переменные необязательно. Во многих сценариях, приведенных в этой книге, переменные не объявляются, они просто используются. То же самое касается
и классов, однако лучше этого не делать. Объявление переменных позволяет легче понять назначение класса. Кроме того, отсутствие явных объявлений является плохим стилем программирования.
При объявлении переменных им можно присвоить значения по умолчанию. Такие значения могут быть лишь самыми простыми и не должны содержать вычисляемых выражений.
Приведенные ниже примеры помогут лучше разобраться в этом.
| |
|
|
Следующие выражения можно использовать в качестве значений по умолчанию:
var $color = "черный";
var $gas = 10;
var $tires = 4; |
Эти выражения нельзя использовать в качестве значений по умолчанию:
var $color = "синий"." черный";
var $gas = 10-3;
var $tires = 2*2; |
В качестве переменных можно также использовать массивы, в которых содержатся простые значения.
var $doors = array("передняя часть", "задняя часть");
Значения соответствующих переменных класса при создании объекта можно задавать или
изменять, используя конструктор (который описывается ниже, в разделе "Создание конструктора") или любой другой метод, предназначенный для этих целей.
Использование переменной $this
Внутри класса специальная переменная $this представляет собой ссылку на этот же
класс. При этом ее нельзя использовать вне класса. Эта переменная обеспечивает доступ
к переменным и методам класса внутри самого класса.
Для использования переменной $this можно использовать следующий синтаксис:
$this->имя_переменной
Например, для доступа к атрибуту $gas класса Car следует воспользоваться выражением
$this->gas
Таким образом, с помощью переменной $this можно манипулировать атрибутом $gas
внутри класса, как, например, в следующих примерах:
$this->gas = 20;
if($this->gas > 10)
$product[$this->size] = $price
Как видно в приведенных примерах, выражение $this->имя_переменной пспользуется внутри класса точно так же, как и переменная $имя_переменной в сценарии.
Обратите внимание на то, что символ $ используется только перед именем this, а не
gas. Если воспользоваться выражением $this->$gas, оно будет неправильно проинтерпретировано. При этом может возникнуть (а может, и нет) ошибка, однако в любом случае
это не будет иметь никакого отношения к ссылке на переменную $gas текущего класса.
Добавление методов
Методы определяют функциональность объектов и определяются точно так же, как и обычные функции. Например, для класса "автомобиль" может понадобиться метод, который будет
обеспечивать заправку бензином его бака. В классе может содержаться переменная $gas, значение которой будет соответствовать количеству бензина, содержащегося в баке в текущий момент. Можно определить метод, который будет добавлять к этой переменной значение
$amount (количество заправленного бензина). Это можно осуществить следующим образом:
class Car
{
var $gas = 0;
function addGas($amount)
{
$this->gas = $this->gas + $amount;
echo "в бак залито $amount галлонов бензина";
}
}
Функция addGas() выглядит как обычная функция, но поскольку она находится внутри
класса, то является его методом.
В языке РНР определено несколько специальных методов, имена которых начинаются
с двух символов подчеркивания (__). В этой главе рассматриваются три из них: construct,
destruct и clone. Поэтому имена своих методов лучше не начинать с двух символов подчеркивания, если только не переопределяется один из специальных методов.
Создание конструктора
Конструктор (constructor) — это специальный метод класса, который выполняется при
создании объекта. Конструктор использовать необязательно, если при создании объекта не
нужно присваивать значения атрибутам или выполнять какие-либо действия. В классе мсжет
содержаться только один конструктор.
Конструктор имеет специальное имя ( __construct), поэтому интерпретатор РНР всегда
знает, какой метод нужно выполнять при создании объекта. Конструктор имеет примерно
следующий вид:
function __construct()
{
$this->gas =10; # бак заполнен полностью
$this->openDoor();
}
Этот конструктор создает новый автомобиль (т.е. объект) с полным баком и открытой дверью.
 |
До версии РНР5 имя конструктора совпадало с именем класса. При этом классы, созданные для более ранних версий интерпретатора, могут использоваться и в РНР5.
При создании объектов интерпретатор РНР5 сначала пытается вызвать конструктор с именем __construct(). Если такой метод отсутствует, вызывается метод
с именем, совпадающим с именем класса. |
Собирая все вместе
Класс должен содержать столько атрибутов и методов, сколько необходимо. Эти методы
могут быть как простыми, так и сложными. Однако цель объектно-ориентированного программирования заключается в том, чтобы сделать методы как можно более рациональными.
Лучше использовать несколько небольших функций и вызывать один метод внутри другого,
а не реализовывать все требуемые действия в одной функции. Рассмотрим следующий пример:
 |
class MessageHandler
{
var $message = "Сообщения нет";
function __construct($message)
{
$this->message = $message;
}
function displayMessage()
{
echo $this->message. "\n";
}
} ?>
$phone_form = new MessageHandler("СоОбЩеНиЕ");
$phone_form->displayMessage();
?> |
 |
class MessageHandler2
{
var $message = "Сообщения нет";
function __construct($message)
{
$this->message = $message;
echo $this->message. "\n";
}
} ?>
$phone_form = new MessageHandler2("СоОбЩеНиЕ");
?> |
Класс MessageHandler имеет один атрибут $message, в котором хранится сообщение,
и один метод displayMessage, предназначенный для его вывода.
Предположим, необходимо добавить метод, который будет преобразовывать символы сообщения в нижний регистр, а затем выводить их на экран. Это можно выполнить таким образом:
 |
class MessageHandler1
{
var $message = "Сообщения нет";
var $ctoimost = 0;
function __construct($message,$ctoimost)
{
$this->message = $message.$ctoimost
;
}
function displayMessage()
{
echo $this->message. "\n";
}
function lowerCaseMessage()
{
$this->message = strtolower($this->message);
$this->displayMessage() ;
}
}
?>
$phone_form = new MessageHandler1("MassAgE",666);
//
$phone_form->displayMessage();$phone_form-> lowerCaseMessage();
$phone_form->displayMessage();
?> |
Обратите внимание на метод lowerCaseMessage(). Поскольку класс уже содеркит
метод для вывода сообщения, в новом методе lowerCaseMessage() используется именно
этот метод. Если при создании нового метода оказалось, что часть его кода уже использовалась в другом методе, нужно выполнить их повторное проектирование. В одном и том же
классе не должно быть повторяющегося кода.
В листинге 9.1 приведен более сложный пример класса, который можно использовать для
создания HTML-форм. Для некоторого упрощения примера в форме содержатся только текстовые поля.
Листинг 9.1. Пример сценария, в котором содержится класс для создания форм
В созданном классе Form содержатся четыре атрибута (свойства) и три метода. Ниже
приведен перечень атрибутов.
| |
|
|
$fields. Массив, содержащий поля формы, заполняемые пользователем. |
$processor. Имя сценария, который обрабатывает данные, введенные пользователем в форме. Значение этой переменной используется в атрибуте action дескриптора form. |
|
$submit. Текст, который отображается на кнопке submit. |
|
$Nfields. Количество полей, добавленных в форму в текущий момент. |
В классе Form содержатся следующие методы.
| |
|
|
__construct(). Конструктор, который во время создания объекта присваивает атрибутам $processor и $submit значения, переданные пользователем. |
addField(). Добавляет имя и метку поля в массив $fields. Например, если пользователь добавил в форму поля для ввода имени и фамилии, то этот
массив будет иметь следующий вид:
$fields[1][name]=first_name
$fields[l][label]=First Name
$fields[2][name]=last_name
$fields[2][label]=Last Name
и т.д. |
|
displayForm(). Используется для вывода формы на экран, т.е. дескрипторов HTML, необходимых для создания формы. При этом используются имена
и метки полей, содержащиеся в атрибутах класса. |
В следующем разделе описываются принципы использования классов в сценарии, в том
числе класса Form, приведенного в листинге 9.1.
Класс, который используется в сценарии, должен быть в него каким-то образом добавлен.
Обычно он размещается в отдельном файле и с помощью функции include() подключается к основному сценарию.
Для того чтобы использовать объект, его необходимо сначала создать. После этого объект
может выполнять любые методы, определенные в классе. Процесс создания объектов называется инстанцированием (instantiation). Аналогично тому, как выкройка используется для шитья
большого количества похожей, но все же по-своему индивидуальной одежды, точно так же на
основе классов создаются объекты. Для создания объекта используется следующий синтаксис:
$имя_объекта = new имяКласса(значение, значение, ...);
$Joe = new Person("мужчина");
$car_Joe = new Car("красный");
$car_Sam = new Car("зеленый");
$customerl = new Customer("Смит", "Джо", $custID);
При создании объекта вызывается конструктор, в результате чего объект связывается
с переменной с заданным именем. После этого для вызова метода класса можно воспользоваться следующими выражениями:
$Joe->goToWork();
$car_Joe->park("запрещено");
$car_Sam->paintCar("синий") ;
$name = $customerl->getName ();
Разные объекты, созданные на основе одного и того же класса, независимы друг от друга.
Например, если автомобиль Сэма перекрасить в голубой цвет, то автомобиль Джо никак не
изменит свой цвет и будет оставаться красным. Джо покупает талон для парковки, но это никак не повлияет на Сэма (и на его автомобиль).
В листинге 9.2 приведен пример использования класса Form, который был создан в предыдущем разделе и представлен в листинге 9.1
Листинг 9.2. Сценарий создания формы с помощью класса Form
 |
ФopMa для добавления телефонного
HOMepaПожалуйста, заполните поля данной формы: |
Сначала с помощью функции require_once() файл form.inc с описанием класса
Form включается в сценарий. На его основе создается объект с именем $phone_form. 3aтем
в форму добавляются три поля, после чего форма выводится на экран. Следует заметить, что
в приведенном фрагменте использовалось несколько дополнительных дескрипторов HTML,
которые можно было бы разместить и в методе displayForm ().
Скрытые свойства и методы
Свойства и методы класса могут быть как открытыми (public), так и закрытыми
(private). Открытые свойства и методы доступны извне класса, т.е. из сценария, в котором используется данный класс, или из другого класса. Рассмотрим пример класса, атрибут и метод которого являются открытыми:
|