Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

web - tec / PHP 5 для начинающи

.pdf
Скачиваний:
73
Добавлен:
12.06.2015
Размер:
26.79 Mб
Скачать

502 Глава 12

Затем добавляется метод+деструктор __destruct. В данном примере он выполня+ ет две функции:

1.Деструктор проверяет наличие изменений в свойствах объекта. Если какие+ либо изменения есть, то формируется UPDATE+оператор для ввода в базу дан+ ных. UPDATE+оператор обновляет только те значения, которые действительно были изменены.

2.Деструктор закрывает соединение с сервером баз данных. После записи изме+ нений в базу данных остается открытым ресурс соединения с ней, который не+ обходимо закрыть. Соединение первоначально было открыто в конструкторе. Сохраняя данное соединение открытым в течение времени существования объекта, можно использовать это соединение для выполнения других запросов, избегая при этом издержек открытия новых соединений. Закрытие соединения по окончании работы объекта позволяет сэкономить системные ресурсы.

После того как объект $obj2 вывел приветственное сообщение, сценарий завер+ шает работу и PHP+интерпретатор начинает процесс уничтожения всех неиспользуе+ мых переменных, включая $obj и $obj2. В ходе этого процесса проверяется наличие определения метода __destruct. Если данный метод определен, то он вызывается для объекта перед удалением этого объекта из системной памяти. В данном примере это происходит в конце сценария testuser.php.

Насколько действенной может быть эта методика? Сценарий testuser.php по+ зволяет извлечь из базы данных информацию о пользователе, изменить какое+либо свойство данного пользователя и автоматически записать изменения обратно в базу данных, используя всего две строки кода. Если никаких изменений не было, как в случае со вторым объектом ($obj2), то базу данных можно не использовать, сокращая, таким образом, нагрузку на сервер баз данных и повышая производительность приложения.

Пользователи объекта не обязательно должны понимать его внутреннее устройст+ во. Если ведущий разработчик написал класс User, он может передать объект нович+ ку, который, возможно, не разбирается в SQL, а новичок в свою очередь сможет при+ менить объект, даже не зная о том, откуда поступают данные или как они должны сохраняться. Фактически можно заменить источник данных с MySQL+базы на PostGreSQL+базу или даже на XML+файл, не сообщая об этом новичкам и даже не из+ меняя ни одной строки кода, использующего данный класс.

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

Предположим, что требуется создать приложение для контроля складских запасов торгового представительства автомобильного завода. Для реализации подобного приложения, вероятно, понадобится создать такие классы, как Sedan, PickupTruck и MiniVan, которые будут соответствовать одноименным типам автомобилей в рее+ стре торгового представительства. Приложение должно отображать не только коли+ чество имеющихся автомобилей, но и их характеристики, чтобы продавцы могли предоставлять соответствующую информацию клиентам.

Седан ++++++ четырехдверный автомобиль, и желательно было бы записывать в базу количество задних мест, а также объем багажника для каждого седана. Пикап не имеет багажника, но у него есть грузовой отсек определенной емкости и грузоподъемности. Фургон (MiniVan) имеет одну или две сдвижные двери и несколько посадочных мест.

Однако каждое из этих транспортных средств на самом деле представляет собой просто разные типы автомобилей, и поэтому в разрабатываемом приложении они будут

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

иметь множество общих характеристик, например, цвет, марку, модель, год выпуска, номер и т.д. Чтобы гарантировать, что каждый из классов имеет такие свойства, мож+ но было бы скопировать код, создающий данные свойства, во все файлы, содержащие определения классов. Однако, как уже отмечалось, одно из преимуществ OO+програм+ мирования заключается в возможности повторного использования кода. Следовательно, можно не копировать код, создающий свойства, а вместо этого повторно использовать свойства и методы данных классов. Такую возможность предоставляет процесс, кото+ рый называется наследованием (inheritance). Наследование ++++++ возможность класса полу+ чать методы и свойства родительского по отношению к нему класса.

Наследование позволяет определить базовый класс ++++++ в данном случае класс Automobile ++++++ и указать, что другие классы представляют собой типы автомобилей, и поэтому имеют все те же свойства и методы, которые реализованы в классе Automobile. Таким образом, седан ++++++ это автомобиль, и поэтому он автоматически наследует все свойства и методы, определенные в классе Automobile, и копировать код для этого не требуется. Придется только написать дополнительные свойства и методы класса Sedan, которые не+ характерны для всех остальных автомобилей. Все что требуется сделать, это определить различия; сходные черты всех классов будут унаследованы от базового класса.

Возможность повторного использования кода ++++++ не единственное достоинство применения наследования; есть еще одно значительное преимущество. Предполо+ жим, что существует класс Customer, имеющий метод buyAutomobile. Данный ме+ тод принимает один параметр ++++++ объект класса Automobile и распечатывает все не+ обходимые документы для оформления продажи автомобиля, а затем уменьшает количество машин, оставшихся на складе. Так как все седаны, пикапы и фургоны яв+ ляются автомобилями, можно передавать объекты соответствующих классов в функ+ цию, которая ожидает объект класса Automobile. Так как объекты трех указанных типов наследуют характеристики более общего родительского класса, известно, что все они будут иметь одинаковый базовый набор свойств и методов. До тех пор, пока будут нужны только методы и свойства, общие для всех автомобилей, можно будет принимать объекты любого класса+наследника Automobile.

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

ВPHP можно указать, что класс представляет собой подмножество другого класса,

спомощью ключевого слова extends (расширяет), которое сообщает PHP+машине, что объявляемый класс должен наследовать все свойства и методы родительского по отношению к нему класса, и что кроме этого в объявляемый класс будут добавлены дополнительные свойства или методы.

Если бы пришлось создавать приложение, ‘‘имитирующее’’ животных в зоопарке, то, вероятно, понадобилось бы использовать классы Cat (кошка), Lion (лев) и Cheetah (гепард). Прежде чем писать какой+либо код, следует спланировать иерархию классов, используя UML+диаграммы. Эти диаграммы впоследствии послужат начальной точкой для написания кода и документации разрабатываемых классов. (Язык UML подробнее рассматривается в главе 13.) Диаграмма классов должна отражать родительский класс Cat и его подклассы Lion и Cheetah, наследующие его свойства и методы. Пример UML+диаграммы показан на рис. 12.1.

504 Глава 12

Cat

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Lion

 

 

 

 

 

 

 

Cheetah

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 12.1.

 

 

 

 

 

Оба класса, Lion и Cheetah, ++++++ наследники класса Cat, но в классе Lion, кроме того,

реализовано свойство maneLength (длина гривы) и метод roar() (рычать), тогда как

в класс Cheetah добавлено свойство numberOfSpots (количество пятен).

Класс Cat может быть реализован следующим образом:

<?php

 

 

 

 

 

class Cat {

//вес в кг

public $weight;

public $furColor;

//масть

public $whiskerLength;

//длина усов

public $maxSpeed;

//максимальная скорость, км/час

public function eat() { //код метода "есть"...

}

public function sleep() { //код метода "спать"...

}

public function hunt(Prey $objPrey) {

//код для метода "охотиться" за объектами //типа Prey (добыча), который не определен...

}

public function purr() { //функция мурлыкать print "муррррррр...";

}

}

?>

В данном простом классе устанавливаются общие для всех кошек свойства и методы. Чтобы создать классы Lion и Cheetah, можно было бы скопировать в них весь код класса Cat, но такой подход привел бы к двум проблемам. Во+первых, если в классе Cat

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

обнаружится дефект, то этот дефект также придется исправлять в классах Lion и Cheetah. Это увеличивает объем необходимой работы, а не уменьшает его (уменьшение объема ра+ боты ++++++ одно из основных преимуществ объектно+ориентированного подхода).

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

//. . .дополнительный блок кода //функция "погладить кошечку"

public function petTheKitty(Cat $objCat) { $objCat->purr();

}

Хотя гладить льва или гепарда небезопасно, они, вероятно, промурлыкали бы, ес+ ли бы позволили себя погладить. Должна быть возможность передавать функции petTheKitty() объект класса Lion или Cheetah.

Поэтому необходимо создавать классы Lion и Cheetah другим путем, а, следова+ тельно, использовать наследование. Используя ключевое слово extends и указав имя расширяемого класса, можно легко создать два новых класса, которые будут иметь все те же свойства, что и обычные кошки, а также некоторые дополнительные возмож+ ности. Например:

<?php require_once('class.Cat.php');

class Lion extends Cat {

public $maneLength; //длина гривы, см

public function roar() { print "Рррррррр!";

}

}

?>

Это все. Используя класс Lion, расширяющий класс Cat, можно создать следую+ щий сценарий, testlion.php:

<?php

include('class.Lion.php');

$objLion = new Lion();

$objLion->weight = 200; //кг или ~450 фунтов. $objLion->furColor = 'коричневый'; $objLion->maneLength = 36; //см или ~14 дюймов

$objLion->eat();

$objLion->roar(); $objLion->sleep();

?>

Теперь можно использовать свойства и методы дочернего класса Lion, не перепи+ сывая весь код. Обратите внимание, что ключевое слово extends указывает PHP+ интерпретатору автоматически включить всю функциональность класса Cat наряду со всеми свойствами и методами, характерными для класса Lion. Кроме того, исполь+ зование данного ключевого слова означает, что объект класса Lion также является Cat+объектом и поэтому теперь можно вызывать функцию petTheKitty(), переда+ вая ей Lion+объект, несмотря на то, что в определении данной функции в качестве параметра фигурирует объект класса Cat:

<?php

include('class.Lion.php'); $objLion = new Lion();

506 Глава 12

$objLion->petTheKitty($objLion);

?>

Таким образом, любые изменения в классе Cat автоматически наследуются клас+ сом Lion. Исправление ошибок, изменения внутренней реализации функций или но+ вые методы и свойства передаются подклассам родительского класса. В крупной хо+ рошо организованной объектной иерархии такие изменения могут значительно упростить исправление ошибок и усовершенствование кода. Небольшое изменение родительского класса может сильно повлиять на все приложение.

Практика Создание класса Cheetah

В данном примере показано, как можно использовать метод+конструктор для расши+ рения и специализации класса. Создайте новый файл с именем class.Cheetah.php и введите в него следующий код:

<?php require_once('class.Cat.php');

class Cheetah extends Cat { public $numberOfSpots;

public function __construct() { $this->maxSpeed = 100;

}

}

?>

Введите в файл testcheetah.php следующий код:

<?php require_once('class.Cheetah.php');

function petTheKitty(Cat $objCat) { if($objCat->maxSpeed < 5) {

$objCat->purr();

} else {

print "Невозможно погладить эту кошечку – она двигается со скоростью " . $objCat->maxSpeed . " километров в час!";

}

}

$objCheetah = new Cheetah(); petTheKitty($objCheetah);

$objCat = new Cat(); petTheKitty($objCat);

?>

Как это работает

Вкласс Cheetah добавляется новая общедоступная переменная $numberOfSpots

иконструктор, которого не было в родительском классе Cat. Теперь, когда создается новый объект класса Cheetah, свойству maxSpeed (унаследованному от Cat) при+ сваивается значение 100 км/час, которое примерно соответствует максимальной ско+ рости гепарда на короткой дистанции. Так как стандартное значение этого свойства

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

в классе Cat не указано, в функции petTheKitty() свойство maxSpeed вычисляется равным 0 (фактически Null). Учитывая то, как долго спят домашние кошки, их макси+ мальная скорость, вероятно, стремится к нулю.

Путем добавления новых функций, свойств или конструкторов и деструкторов можно легко расширять функциональность подклассов родительского класса. Ина+ че говоря, с минимальным количеством кода в приложение можно добавлять новые возможности. Более развитый пример рассматривается в следующей главе при расширении класса PropertyObject до классов приложения для работы с кон+ тактной информацией.

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

Переопределение методов

Только лишь тот факт, что дочерний класс наследует от родительского свойства

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

иметоды от родительского класса, который называется Polygon (многоугольник).

Вклассе Polygon должно быть свойство numberOfSides (количество сторон)

иметод getArea (вычислить площадь). Можно вычислить площадь любого много+ угольника, но методы вычисления площади для типов многоугольников могут отли+ чаться друг от друга. Например, для вычисления площади прямоугольника использу+ ется формула w*h, где w ++++++ ширина прямоугольника, а h ++++++ его высота. Площадь треугольника вычисляется по формуле (h/2)*b, где h ++++++ высота треугольника с осно+ ванием b. На рис. 12.2 иллюстрируются оба примера.

Площадь треугольника = (1/2h) x b

h

h

w

Площадь прямоугольника = w x h

Рис. 12.2.

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

Например, в случае класса Rectangle создается два новых свойства, height и width (высота и ширина соответственно), и переопределяется родительская реали+ зация метода getArea (общая формула для вычисления площади сложного много+ угольника, которую реализовывал бы метод getArea класса Polygon, довольно слож+ на, и поскольку данный метод все равно будет переопределен для всех подклассов, здесь

508 Глава 12

углубляться в тонкости геометрических формул нет необходимости). Для класса Triangle, вероятно, добавились бы свойства для хранения информации о трех углах, высоте треугольника и длине его основания, а для переопределения метода getArea использовалась бы высота и длина основания. Используя наследование и подменяя методы родительского класса, можно создать в подклассах их собственные реализа+ ции этих методов.

Функция, которая принимает в качестве параметра многоугольник и должна распе+ чатывать его площадь, автоматически вызывает метод getArea того подкласса класса Polygon, который был ей передан (например, Rectangle или Triangle). Способность объектно+ориентированного языка программирования автоматически во время выпол+ нения программы определять, какой метод getArea необходимо вызвать, называется полиморфизмом (polymorphism). Полиморфизм ++++++ способность приложения выполнять различные функции в зависимости от конкретного объекта, на который воздействует это приложение. В данном случае это означает вызов различных методов getArea.

Подменять метод в подклассе следует тогда, когда реализация этого метода в родительском классе не удовлетворяет требованиям подкласса. Это позволяет специализировать действия подкласса.

Иногда необходимо оставить реализацию какого+либо метода, предоставленную ро+ дительским классом, а в подклассе добавить в этот метод некоторые дополнительные действия. Предположим, например, что существует приложение для контроля членов какой+либо некоммерческой организации. Приложение содержит класс Volunteer, в котором имеется метод signup. Он позволяет добровольцам вступать с состав обще+ ственной миссии, а затем добавляет пользователей в список добровольцев этой миссии.

Однако добровольцами могут оказаться пользователи с некоторыми ограничениями, например, люди с уголовным прошлым, которых не следует допускать к определенным проектам. В таком случае полиморфизм позволяет создать класс RestrictedUser с пе+ реопределенным методом signup, который сначала сверяет ограничения учетной за+ писи пользователя со свойствами проекта и не допускает пользователей к регистрации, если их ограничения запрещают им заниматься определенными видами деятельности. Если пользовательские ограничения не запрещают добровольцам участвовать в данном проекте, то для завершения регистрации вызывается метод родительского класса.

При подмене метода родительского класса не обязательно полностью переписы+ вать этот метод. Можно продолжать использовать родительскую реализацию, добавив дополнительную специализацию для метода подкласса. Это позволяет повторно ис+ пользовать код, а также обеспечить соответствие требованиям бизнес+логики.

Способность одного класса наследовать методы и свойства другого класса является одной из самых выдающихся особенностей объектно+ориентированной системы и позволяет достигать невероятных уровней эффективности и гибкости приложений.

Практика Подмена наследуемых методов

В данном примере создается два класса: Rectangle (прямоугольник) и Square (квадрат). Квадрат является особым видом прямоугольника. Все допустимые для пря+ моугольника операции допустимы и для квадрата, но прямоугольник имеет две раз+ личные длины сторон, а квадрат имеет только одну длину стороны, поэтому некото+ рые операции отличаются.

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

Создайте файл class.Rectangle.php и введите в него следующий код:

<?php

class Rectangle { public $height; public $width;

public function __construct($width, $height) { $this->width = $width;

$this->height = $height;

}

public function getArea() {

return $this->height * $this->width;

}

}

?>

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

Ниже приведен код файла class.Square.php:

<?php require_once('class.Rectangle.php');

class Square extends Rectangle { public function __construct($size) {

$this->height = $size; $this->width = $size;

}

public function getArea() { return pow($this->height, 2);

}

}

?>

В данном классе подменяется конструктор и метод getArea(). У квадрата все сто+ роны должны иметь одинаковую длину. Поэтому для конструктора нужен только один параметр. Если конструктору передается несколько параметров, то все значения по+ сле первого будут проигнорированы.

Проверим работу класса с помощью следующего сценария (testsquare.php):

<?php

require_once ('class.Square.php'); $obj = new Square(7); $a = $obj->getArea(); echo "$a";

?>

Данный код распечатывает ожидаемое значение площади квадрата с длиной сто+ роны 7.

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

рассматривается в документации по встроенной функции func_get_args().

510Глава 12

Вклассе Square также переопределяется функция getArea(). Реализация класса Rectangle сама по себе возвращает идеально верный результат для вычисления площади квадратов ++++++ метод getArea() был подменен для повышения производи+ тельности приложения (хотя в рассматриваемом случае выигрыш производительно+ сти мизерный). PHP+интерпретатор быстрее определяет значение одного свойства и вычисляет квадрат числа, чем два свойства и их произведение.

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

Сохранение функциональности родительского класса

Иногда необходимо сохранить функциональность, предоставленную родительским классом, т.е. не подменять его функции полностью, а лишь добавить в них некоторые операции. Можно было бы скопировать весь код из родительского метода в метод подкласса, но, как уже было показано выше, OO+свойства PHP предлагают более ра+ зумные способы решения данной задачи, чем просто копирование кода.

Для того чтобы вызвать функцию, предоставленную родительским классом, необ+ ходимо использовать следующий синтаксис: parent::[имя_функции] . Чтобы просто добавить дополнительные действия в метод, сначала вызывается parent::[имя_ функции], а затем добавляется дополнительный код. При расширении функции та+ ким способом рекомендуется всегда сначала вызывать родительский метод, а затем выполнять остальные действия. Это гарантирует, что любые изменения работы роди+ тельского метода не нарушат код подкласса.

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

Рассмотрим пример использования данной методики.

Практика Сохранение функциональности родительского метода

Вданном примере задействовано два класса: Customer и SweepstakesCustomer.

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

Создайте файл class.Customer.php и введите в него следующий код:

<?php

class Customer { public $id;

public $customerNumber; public $name;

public function __construct($customerID) { //получение из базы данных информации о покупателе

//

//Представленные ниже значения, очевидно, взяты не из //базы данных, но в реальном приложении их

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

//следовало бы извлекать из базы данных. $data = array(); $data['customerNumber'] = 1000000; $data['name'] = 'Jane Johnson';

//Присваиваем объекту значения из базы данных $this->id = $customerID;

$this->name = $data['name']; $this->customerNumber = $data['customerNumber'];

}

}

?>

Создайте файл class.SweepstakesCustomer.php и введите в него следующий код:

<?php require_once('class.Customer.php');

class SweepstakesCustomer extends Customer { public function __construct($customerID) { parent::__construct($customerID);

if($this->customerNumber == 1000000) {

print "Поздравляем, $this->name! Вы – наш миллионный покупатель! " . "Вы выигрываете приз – годовой запас рыбных палочек! ";

}

}

}

?>

Чтобы посмотреть данный класс в работе, создайте файл testCustomer.php и введите в него следующий код:

<?php

require_once('class.SweepstakesCustomer.php'); //поскольку файл class.SweepstakesCustomer.php //уже включает файл class.Customer.php,

//нет необходимости еще раз подключать здесь последний.

function greetCustomer(Customer $objCust) {

print "$objCust->name, добро пожаловать в наш магазин снова!";

}

//Измените данное значение, чтобы изменить класс, //используемый для создания объекта Customer

$promotionCurrentlyRunning = true;

if($promotionCurrentlyRunning) {

$objCust = new SweepstakesCustomer(12345);

}else {

$objCust = new Customer(12345);

}

greetCustomer($objCust);

?>

Запустите сценарий testCustomer.php в браузере, присвоив сначала перемен+ ной $promotionCurrentlyRunning значение false, а затем true. В последнем слу+ чае должно появиться сообщение о выигрыше приза.

Соседние файлы в папке web - tec