ProfiPHPProfiPHP
Категория: PHP для чайников

Объектно-ориентированное программирование на РНР

С момента создания языка РНР он рассматривался как простой процедурный язык написания сценариев. Только начиная с версии PHP 4, в нем появились объектно-ориентированные свойства. В PHP 5 возможности объектно-ориентированного программирования на языке РНР были существенно расширены вместе с повышением быстродействия разрабатываемых программ, что сделали этот язык программирования по-настоящему объектно-ориентированным.

Введение в объектно-ориентированное программирование

Подход объектно-ориентированного программирования заключается в использовании объектов и классов и разрабатывается путем моделирования процессов в реальной предметной области.
В объектно-ориентированном программировании основными элементами программы являются объекты (object), которые представляются реальными объектами, задействованными при решении реальной проблемы. Например, если приложение относится к предметной области продажи автомобилей, то, возможно, в качестве объектов будут выбраны автомобили и покупатели.

Объекты и классы

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

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

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

Свойства объектов

Объекты имеют свойства (property), также называемые атрибутами (attribute). Автомобиль может быть белым, зеленым или красным. Такие свойства автомобиля, как цвет, размер или модель, являются внутренними характеристиками объекта. Обычно свойства класса задаются в виде переменных. Например, атрибут цвета может храниться в объекте в виде переменной с отличительным именем, например $color.

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

Методы объектов

Методы объектов это действия, которые могут выполнять объекты, иногда рассматриваются как его обязанности (responsibility). Например, объект "автомобиль" может перемещаться вперед и назад, останавливаться и парковаться. Каждое действие (обязанность), которое объект может выполнять, реализуется в классе в виде метода (method).

Метод в языке PHP имеет тот же синтаксис, что и функция. Разница заключается в том, что методы находятся внутри класса. Они не могут быть вызваны независимо от класса — интерпретатор РНР не позволит сделать это. Функции такого вида могут использоваться только в контексте объекта.

Как правило, при создании методов им присваивают имена, соответствующие выполняемым действиям, например getColor().

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

Хорошо спроектированный объект должен содержать все необходимое для выполнения своих обязанностей, но при этом не иметь ничего лишнего. Он не должен выполнять
обязанности других объектов.

Наследование

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

Классы-потомки наследуют все атрибуты и методы родительского класса. Однако они могут иметь и свои собственные свойства.

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

Разработка объектно-ориентированных программ

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

Выбор объектов

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

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

Выбор свойств и методов для каждого объекта

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

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

Создание и использование класса

После выявления объектов и их свойств можно приступать к их созданию и использованию. Для создания и использования объекта нужно выполнить следующие действия.

1. Использовать ключевое слово Class. Ключевое слово Class является основным при создании класса. С ним связывается блок инструкций со свойствами и методами класса.

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

3. Создать объект в сценарии. На основе описания класса можно создавать объекты, то есть выполнять инстанцирование (instantiation).

4. Использовать объект. После создания нового объекта можно приступать к его пользованию. При этом можно использовать любые методы класса, определенные в соответствующем блоке при его описании.

Определение класса

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

Создание класса

Выражение 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() выглядит как обычная функция, но поскольку она находится внутри класса, то является его методом.

В языке РНР определено несколько специальных методов, имена которых начинаются с двух символов подчеркивания (__). Поэтому имена своих методов лучше не начинать с двух символов подчеркивания, если только не переопределяется один из специальных методов.

Создание конструктора

Конструктор (constructor) — это специальный метод класса, который выполняется при создании объекта. Конструктор использовать необязательно, если при создании объекта не нужно присваивать значения атрибутам или выполнять какие-либо действия. В классе может содержаться только один конструктор.

Конструктор имеет специальное имя (__construct), поэтому интерпретатор РНР всегда знает, какой метод нужно выполнять при создании объекта. Конструктор имеет примерно следующий вид:
function __construct( )
{
$this->gas = 10; # бак заполнен полностью
$this->openDoor( );
}
Этот конструктор создает новый автомобиль (то есть объект) с полным баком и открытой дверью.
До версии РНР 5 имя конструктора совпадало с именем класса. При этом классы, созданные для более ранних версий интерпретатора, могут использоваться и в РНР 5. При создании объектов интерпретатор РНР 5 сначала пытается вызвать конструктор с именем __construct() . Если такой метод отсутствует, вызывается метод с именем, совпадающим с именем класса.

Использование класса

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

Для того чтобы использовать объект, его необходимо сначала создать. После этого объект может выполнять любые методы, определенные в классе. Процесс создания объектов называется инстанцированием (instantiation). Аналогично тому, как выкройка используется для шитья большого количества похожей, но все же по-своему индивидуальной одежды, точно так же на основе классов создаются объекты. Для создания объекта используется следующий синтаксис:
$имя_объекта = new имя Класса(значение, значение, ... );
$Joe = new Person( 'мужчина' );
$car_Joe = new Car( 'красный' );
$car_Sam = new Car( 'зеленый' );
$customer1 = new Customer( 'Смит', 'Джо', $custID );
При создании объекта вызывается конструктор, в результате чего объект связывается с переменной с заданным именем. После этого для вызова метода класса можно воспользоваться следующими выражениями:
$Joe->goToWork( ); 
$car_Joe->park( 'запрещено' );
$car_Sam->paintCar( 'синий' );
$name = $customer1->getName( );
Разные объекты, созданные на основе одного и того же класса, независимы друг от друга. Например, если автомобиль Сэма перекрасить в голубой цвет, то автомобиль Джо никак не изменит свой цвет и будет оставаться красным. Джо покупает талон для парковки, но это никак не повлияет на Сэма (и на его автомобиль).

Скрытые свойства и методы

Свойства и методы класса могут быть как открытыми (public), так и закрытыми (private). Открытые свойства и методы доступны извне класса, то есть из сценария, в котором используется данный класс, или из другого класса. Рассмотрим пример класса, атрибут и метод которого являются открытыми:
class Car
{
var $gas = 0;
function addGas( $amount )
{
$this->gas = $this->gas + $amount;
echo 'в бак залито $amount галлонов бензина';
}
}
Теперь к открытому атрибуту класса $gas можно получить доступ из-за его пределов следующим образом:
$mycar = new Car;
$gas_amount = $mycar->gas;
После выполнения этого фрагмента в переменной $gas_amount будет содержаться значение атрибута $gas объекта mycar. Значение атрибута можно также и изменить извне класса:
$mycar->gas = 20;
Прямой доступ к атрибутам класса считается плохим стилем программирования. Любое взаимодействие объекта и внешнего сценария или другого класса должно осуществляться лишь посредством методов. Метод addGas() из приведенного класса Car является открытым и применяется для 'заправки' автомобиля (то есть для увеличения значения атрибута $gas):
$new_car = new Car;
$new_car->addGas(5);
Язык PHP позволяет ограничить доступ к атрибутам и методам класса. Это можно осуществить следующими способами.

- Спецификатор Private (закрытый) запрещает доступ к атрибутам и методам класса из-за пределов класса, сценариев или других классов.

- Спецификатор Protected (защищенный) предоставляет доступ к защищенным свойствам и методам класса только для его классов-наследников. в остальных случаях доступ запрещен.

Для того чтобы атрибут стал скрытым, следует воспользоваться выражением:
private $gas = 0;
При попытке прямого доступа к скрытому атрибуту отобразится сообщение об ошибке.

Теперь изменить значение атрибута $gas класса Car можно только с помощью метода addGas(), поскольку он является частью класса Car и, следовательно, имеет доступ к соответствующему скрытому атрибуту.

Подобно атрибутам, методы класса также могут быть скрытыми (private) или защищенными (protected). Однако в классе Car метод addGas() является открытым. В реальных программах нужно быть уверенным в том, что за бензин, которым заправлен автомобиль, уплачено и что он не взялся неизвестно откуда (например, был украден). Поэтому класс Car следует переписать следующим образом:
class Car
{
private $gas = 0;
private function addGas( $amount )
{
$this->gas = $this->gas + $amount;
echo 'в бак залито $amount галлонов бензина';
}
function buyGas( $amount )
{
$this->addGas( $amount );
}
}
Теперь единственный возможный способ изменения значения атрибута $gas класса Car (то есть заправки автомобиля) заключается в использовании метода buyGas(). В свою очередь, этот метод использует скрытый метод addGas(). Прямой вызов метода addGas() приведет к возникновению ошибки.
$new_car = new Car;
$new_car->addGas(5);
Для того чтобы этого не произошло, необходимо воспользоваться методом buyGas():
$new_car = new Car;
$new_car->buyGas(5);
В результате получим следующий результат:
в бак залито 5 галлонов бензина
Хороший стиль программирования заключается в сокрытии как можно большего количества деталей реализации класса. Все атрибуты должны быть скрытыми (private), а методы (те из них, которые должны быть открытыми) - public.

В языке РНР атрибуты и методы класса могут быть объявлены открытыми несколькими способами. Это связано с тем, что по умолчанию уровень доступа является открытым. Так, выражение
public $gas = 0;
равносильно:
var $gas = 0;
Использование исключений

Для обработки ошибок в языке РНР используется класс Exception (исключение). Его можно применять для обработки нежелательных событий, которые могут возникнуть в сценарии. Если возникает заранее определенная разработчиком ситуация, то выполняется соответствующая процедура ее обработки. С точки зрения объектно-ориентированного подхода этот процесс называется генерацией исключения.

В классе Car автомобиль может двигаться до тех пор, пока в его баке есть горючее (то есть $gas > 0). Поэтому сценарий должен правильно реагировать на ситуацию, когда значение атрибута $gas станет отрицательным. Это и будет исключением. Для его обработки необходимо воспользоваться классом Exception с помощью следующего фрагмента кода:
$this->gas = $this->gas - 5;
try
{
if ( $this->gas < 0 )
{
throw new Exception( 'Отрицательное количество бензина.' );
}
}
catch ( Exception $e )
{
echo $e->getMessage( );
exit( );
}
В приведенном фрагменте содержатся блоки Try и Catch.

В блоке Try выполняется проверка условия. Если результат проверки условия - TRUE, то генерируется исключение, то есть создается новый объект класса Exception. Этот объект имеет атрибут, в котором хранится сообщение, которое будет использоваться при генерации исключения.

В блоке Сatch исключение перехватывается, и ему присваивается имя . После этого выполняется обработка исключения. В частности, вызывается метод getMesssage() класса Exception. Этот метод возвращает сообщение, заданное при создании объекта Exception, которое выводится с помощью функции Echo.

Если исключение не сгенерировано, блок Catch игнорируется, а выполнение сценария продолжается.

Копирование объектов

Для копирования объектов в языке РНР используется специальный метод "__clone()" (с двумя символами подчеркивания). При необходимости этот метод можно переопределить.

По умолчанию (то есть без переопределения) метод "__clone()" просто скопирует все атрибуты одного объекта в атрибуты другого объекта.

Рассмотрим следующий класс с переопределенным методом "__clone()":
class Car
{
private $gas = 0;
private $color = 'красный';
function addGas( $amount )
{
$this->gas = $this->gas + $amount;
echo 'в бак залито $amount галлонов бензина';
}
function __clone( )
{
$this->gas = 0;
}
}
Используя этот класс, можно создать объект, а затем скопировать его следующим образом:
$firstCar = new Car; 
$firstCar->addGas(10);
$secondCar=$firstCar->__clone( );
После выполнения этого фрагмента кода будут созданы два объекта класса Car.

- $firstCar. Этот автомобиль имеет красный цвет и 10 галлонов бензина, которые были добавлены с помощью метода addGas().

- $secondCar. Этот автомобиль также имеет красный цвет, но 0 галлонов бензина. Объект $secondCar был создан как копия $firstCar с помощью вызова метода "__clone()", который присваивает переменной $gas значение 0 и вообще не устанавливает цвет ($color).

Если в классе Car не переопределить метод "__clone()", интерпретатор РНР создаст его самостоятельно, по умолчанию скопировав все атрибуты объекта $firstCar в $secondCar, то есть автомобиль $secondCar, как и $firstCar, будет иметь красный цвет и 10 галлонов бензина.

Удаление объектов

Удалить ранее созданный объект можно следующим образом:
unset( $objName );
Приведем пример, в котором объект класса Car создается, а затем удаляется.
$myCar = new Car;
unset( $myCar );
После вызова функции Unset объект больше не существует.

В РНР имеется специальный метод "__destruct()", который автоматически вызывается при удалении объекта. Ниже приведен класс, содержащий этот метод.
class Bridge 
{
function __destruct()
{
echo. 'Объект уничтожен';
}
}
При создании объекта класса Bridge, а затем его удалении:
$bigBridge = new Bridge;
unset( $bigBridge );
отобразится следующее сообщение:
Объект уничтожен
Оно отображается вследствие вызова метода "__destruct()" при вызове функции Unset.

Метод "__destruct()" не является обязательным. По необходимости его можно переопределить, как методы "__construct()" и "__clone()", и выполнить некоторые специфические действия при удалении объекта. Например, при удалении объекта может потребоваться закрыть некоторые файлы или переписать информацию в базу данных.

Добавить комментарий

Имя:
Текст комментария: