Определение класса
Наследование свойств: оператор extends
Конструкторы
Доступ к методам предков
Сериализация объектов
Ссылки в конструкторе
Современные воззрения на технологию разработки программного обеспечения не
обошли стороной и РНР, поэтому сегодня вы можете воспользоваться всеми преимуществами объектной модели, которая заимствована из Perl,
Как в Си и Perl, основной «единицей измерения» объектной модели является класс.
Класс представляет собой набор переменных (членов классов) и функций, оперирующих с этими переменными. Для определения класса используется синтаксис,
продемонстрированный в листинге 7.1 .
Листинг 7.1. Определение корзинки покупок для электронного магазина
<?php
class tart {
var $items; // Товары в корзине (это массив)
// Положить $num товаров с кодом $artnr в корзину
function add_item ($artnr. $nura) {
$this->items[$artnr] += $num;
}
// Выбросить $num товаров с кодом $artnr из корзины
function remove_item ($artnr, $num) {
if ($this->items[$artnr] > $num) {
$this->items[$artnr] -= $num;
return true;
} else {
return false;
}
}
}
?>
Этот фрагмент кода содержит определение класса Cart, который содержит одно
поле данных — ассоциативный массив, в котором ключом является артикул (кодовый номер) некоторого товара, а его значением — количество этих товаров, заказанное пользователем. Для манипуляции с этим массивом в определении класса
предусмотрены два метода — add_itern ( ) и remove_itern ( ) , позволяющие добавлять и удалять товары из корзины.
Заметим, что существует несколько ограничений на имена классов. Имя stdClass
используется самой PHP-машиной и зарезервировано для внутреннего использования в системе. Вы не можете создавать или использовать этот класс в своих РНР-программах.
Аналогичным образом вы не можете включать в классы функции с именами _ sleep
и _ wakeup. Они используются для решения некоторых специальных задач. Подавляющему большинству пользователей РНР эти механизмы не интересны, хотя
позже мы все же коснемся их более подробно в разделе 7.5.2.
совет
Вообще говоря, все имена, начинающиеся с _ , интерпретируются как специальные.
Рекомендуется избегать использования идентификаторов переменных и функций, начинающихся с двух символов подчеркивания.
В РНР4 допускается использование только константных инициализаторов для
переменных типа var. Чтобы инициализировать переменные не константными
значениями, вам обязательно нужно определить специальную функцию, так называемый конструктор экземпляра класса (см. раздел 7.2).
Приведенный ниже фрагмент кода работать не будет, так как конструктор в н^м не
определен:
class Cart {
var $todays_date = date("Y-m-d") ;
var $name = $firstname;
var $owner = 'Fred ' . 'Jones';
}
А этот будет работать, как и ожидается:
class Cart {
var $todays_date;
var $name;
var $owner;
function Cart() {
$this->todays_date = date("Y-m-d") ;
$this->name = $GLOBALS[ 'firstname'] ;
/* etc. . . */
}
}
Отличительной чертой классов в РНР является тот факт, что определение класса
фактически вводит в PHP-машину новый тип данных. Для работы с классом вы
должны создать переменную этого типа с помощью оператора new. Вот как он используется:
$cart = new Cart;
$cart->add_1tem("l0", 1);
$another_cart = new Cart;
$another_cart->add_item("0815", 3);
В результате выполнения этого фрагмента будут созданы экземпляры класса Cart,
которые помещаются в переменные $cart и $another_cart. Функция add_ item() ,
вызванная применительно к объекту $cart, добавит в первую корзину одну единичку товара с артикулом «10», а применительно к $another_cart эта же функция добавит во вторую корзину 3 изделия артикула «0815».
Обе корзины — и $cart, и $another_cart— имеют ассоциированные с ними функции add_item() и remove_item() и содержат переменную items. Несмотря на
одинаковые названия, это различные переменные и функции. По большому счету,
их можно рассматривать как файлы с одинаковыми именами, находящиеся в разных каталогах. И так же, как в случае с файловой системой, для доступа к файлу вы должны вводить его полное путевое имя с указанием каталога, если находитесь
за его пределами, или можете ограничиться только именем файла, если этот ката-
лог является текущим.
С точки зрения модели РНР корневым каталогом такой «объектной» файловой
системы является глобальное пространство имен переменных, а в качестве разделителя имен каталогов используется стрелка - >. Таким образом, имена $cart->items
и $anotner_cart->iterns обозначают различные переменные.
Обратите внимание, что переменные обозначаются как $cart->items, a нe $cart->
$iterns. Символ доллара используется как префикс всего имени переменной, а не
отдельных ее фрагментов:
// Вот так правильно
$cart->items = array("10" => 1);
// А так нельзя, поскольку $cart->$items
// превратится в $cart->""
$cart->$items = array("16" => 1);
// правильно, но может привести и к неожиданным
// результатам...
// $cart->$myvar becomes $ncart->items
$myvar = 'items';
$cart->$myvar = array("10" => 1);
Внутри определения класса вы не знаете, под каким конкретно именем экземпляра будет производиться его вызов: неизвестно, выберет ли пользователь имя переменной $cart или предпочтет отечественное $корзина. На этот случай в РНР предусмотрено специальное зарезервированное имя $this, которое обозначает
текущий экземпляр данного класса. При использовании внутри метода конструкции.
$this->items[$artnr] += $num
она будет автоматически интерпретироваться как
$cart->items[$artnr] += $num
при вызове $cart->add_1tem(...) и как
$корзинa->items[$artnr] += $num
при вызове $корзинa->add_i tern (...).
Довольно часто при разработке объектно-ориентированных программ возникает
потребность в нескольких классах, довольно схожих друг с другом, то есть обладающих сходными наборами переменных и функций. С практической точки зрения
оказывается удобным разработать некоторый обобщенный класс, который может
использоваться во всех проектах, и затем адаптировать этот класс к конкретным
нуждам каждого проекта. Мысль эта не нова, и для ее реализации предусмотрен
так называемый механизм наследования, представляющий собой использование
определенных ранее классов в качестве прототипов, с расширением имеющихся
наборов полей и методов. Класс (и все его экземпляры), созданный на базе прототипа, содержит переменные и функции всех своих предков (собственно, это и вкладывается в понятие «наследования»). При этом, правда, вы не можете лишить потомка какой-либо переменной или функции, свойственной предку, но вы всегда
можете просто не использовать не нужные вам определения. Кроме того, в отличие от C++ и Лиспа в РНР не реализован механизм множественного наследования: у класса может быть только один предок.
Для включения механизма наследования используется оператор extends, который применяется следующим образом:
class Named_Cart extends Cart {
var $owner;
function set_owner ($name) {
$this->owner = $name;
}
}
В этом фрагменте мы вводим определение класса Named_Cart, которое содержит
все переменные и функции класса Cart и дополнительно — переменную $owner и
функцию set_owner ( ) . Создание «именованной корзинки» производится привычным образом, зато теперь вы можете не только управлять содержимым корзины,
но и устанавливать или считывать идентификатор ее владельца:
$ncart = new Named_Cart; // Создаем корзину "нового поколения";
$ncart->set_owner ("Вован"); // Устанавливаем имя пользователя
print $ncart->owner ; // Выводим имя пользователя на печать
$ncart->add_item ("10", 1); // Помещаем в корзинку
примечание
Прежде всего позвольте сделать одно предупреждение. Конструкторы экземпляров
классов в РНР 3 и РНР 4 работают no-разыому. Настоятельно рекомендуется использовать новый подход, который совместим со старой версией. Обратное, к сожалению,
неверно.
Как уже указывалось ранее, .конструкторы представляют собой специальные
функции класса, которые автоматически вызываются при создании нового экземпляра класса с помощью оператора new. В РНР 3 функция интерпретировалась
как конструктор, если по аналогии с C++ ее имя просто совпадало с именем класса. А в РНР 4 функция становится конструктором, если ее имя совпадает с классом, но при этом она определена внутри самого класса. Разница, казалось бы,
ерундовая, но при переносе программ с одной версии на другую может сыграть роковую роль...
Вот, для начала, совместимое решение, которое работает и в третьей, и в четвертой версиях:
class Auto_Cart extends Cart {
function Auto_Cart () {
$this->add_item ("10",1);
Здесь мы определяем класс Auto_Cart, который отличается от предка тем, что автоматически «заталкивает» в корзину покупателя некий товар с артикулом «10».
Эта операция, как и следует ожидать, выполняется при каждом создании экземпляра класса с помощью пен.
Конструкторы принимают аргументы, которые могут быть необязательны-ми (то есть использовать значения по умолчанию), что значительно упрощает
практическую работу с классами. Приведенный ниже фрагмент демонстрирует эту
идею:
// Работает в РНР 3 и РНР 4.
class Constructor_Cart extends Cart {
function Constructor_Cart ($item = "10", $num = 1) {
$this->add_1tem ($item, $num) ;
}
}
// Автоматически впихиваем в корзину старое барахло...
$default_cart = new Constructor_Cart;
// Серьезным клиентам предлагаем совсем другой товар...
$different_cart = new Constructor_Cart ("26", 17;)
7.3.1. Наследование конструкторов
В предыдущей версии РНР 3 на работу унаследованных конструкторов накладывалось несколько ограничений. В приведенном ниже примере демонстрируется
одно из различий в реализации объектной модели РНР-машины:
class A {
cfunction А() {
c echo "Я конструктор класса А/. <br>\n";
}
}
class В extends A {
function C() {
"А я бедная обычная функция. <br >\n" ;
}
}
// При запуске в РНР 3 конструктор не вызывается.
$b = new В;
При запуске в РНР 3 никакой конструктор запускаться на выполнение не будет.
Основное правило РНР 3 звучит следующим образом.
Конструктор представляет собой функцию, имя которой совпадает с именем класса.
А поскольку имя класса равно В и функция В ( ) в определении класса отсутствует,
то никакого конструктора с этим классом просто не связывается.
В РНР 4 на смену этому правилу пришло другое.
Если в определении класса отсутствует собственный конструктор, производится
вызов конструктора его предка, если такой определен.
Поэтому в результате выполнения этого фрагмента вы получите сообщение:
Я конструктор класса A.
7.3.2. Присвоение прав конструктора
Еще одной ошибкой, которая, несмотря на заверения разработчиков, сохранилась
в РНР 4, является возможность неожиданного попадания в категорию конструк
тора обычных функций, которые для этой цели совершенно не были предназначены. Взгляните на приведенный ниже фрагмент:
class A {
function A() {
echo "Я конструктор класса A.<br>\n";
}
function B() {
echo "А я обычная функция В в классе А.<br>\n";
echo "Я на работу конструктора в А не подряжалась.<br>\n";
}
}
class В extends A {
function C() {
echo "А я обычная бедная функция в В.<br>\n";
}
}
// В РНР 3 В() будет вызвана как конструктор!
$b = new В;
При запуске этой программы в РНР 3 функция В()в классе А внезапно превращается в конструктор, хотя разработчик класса А о такой возможности и не подозревал! Но повторим правило выбора конструктора:
Конструктор представляет собой функцию, имя которой совпадает с именем класса.
А поскольку имя класса равно В и функция В()унаследована от предка, она автоматически интерпретируется как конструктор класса!
В результате при запуске этой программы вы получите на экране результат, приведенный на рис. 7.1.
Рис. 7.1. Неожиданная подмена конструкторов при наследовании
|