Главное меню

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

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

Классы и объекты

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

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

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

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

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

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

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

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

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

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

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

Полиморфизм

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

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

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

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

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

Наследование (inheritance) позволяет создавать иерархические взаимосвязи между классами, используя для этого подклассы (subclasses). Подкласс наследует атрибуты и операции своего суперкласса (superclass). Например, автомобиль и велосипед имеют некоторые общие характеристики. Мы могли бы создать класс транспортных средств, содержащий такие характеристики, как атрибут цвета и операцию передвижения, свойственные всем транспортным средствам, а затем унаследовать от класса транспортных средств классы автомобиля и велосипеда.

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

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

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

Создание классов, атрибутов и операций в РНР

До сих пор мы рассматривали классы на довольно-таки высоком уровне абстракции. Для создания класса в РНР предназначено ключевое слово class.

Структура класса

Минимальный вариант определения класса имеет следующий вид:

class classname

{

}

Чтобы от классов была хоть какая-нибудь польза, они должны иметь атрибуты и операции. Атрибуты создаются путем объявления переменных внутри определения класса с помощью ключевых слов, соответствующих их видимости: public, private или protected. Они будут рассмотрены ниже в данной главе. Следующий код создает класс classname с двумя общедоступными атрибутами $attribute1 и $attribute2:

class classname {

var $attribute1;

var $attribute2;

}

Операции создаются за счет объявления функций внутри определения класса. Следующий код создает класс classname с двумя операциями, которые не выполняют никаких действий. Операция operation1() не принимает никаких параметров, а операция operation2() принимает два параметра:

class classname {

function operation1() {

}

function operation2($param1, $param2) {

}

}

Конструкторы

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

Конструктор объявляется так же, как другие операции, но имеет специальное имя __construct(). Хотя конструктор можно вызывать вручную, его основное значение заключается в том, что он автоматически вызывается в момент создания объекта. Следующий код объявляет класс с конструктором:

class classname {

function __construct($param) {

echo "Конструктор вызван с параметром ".$param."<br />";

}

}

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

Деструкторы

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

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

Создание экземпляров класса

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

Приведенный ниже код объявляет класс classname с конструктором, а затем создает три объекта типа classname:

class classname {

function __construct($param) {

echo "Конструктор вызван с параметром ".$param."<br />";

}

}

$a = new classname ("Первый") ;

$b = new classname("Второй");

$c = new classname();

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

Конструктор вызван с параметром Первый

Конструктор вызван с параметром Второй

Конструктор вызван с параметром

Использование атрибутов класса

Внутри класса вы получаете доступ к специальному указателю с именем $this. Если атрибут вашего текущего класса имеет имя $attribute, он указывается при занесении или выборке значения в операции внутри класса следующим образом:

$this->attribute

Следующий код служит примером установки атрибутов и обращения к ним внутри класса:

class classname {

var $attribute;

function operation($param) {

$this->attribute = $param

echo $this->attribute;

}

}

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

class classname {

var $attribute;

}

$a = new classname();

$a->attribute = "value";

echo $a->attribute;

Прямой доступ к атрибутам за пределами класса – обычно не особенно хорошая идея. Одно из преимуществ объектно-ориентированного подхода заключается в том, что он поощряет инкапсуляцию. Для этого предназначены функции __get и __set. Если вместо прямого доступа к атрибутам класса создать функции доступа (accessor function), весь доступ будет осуществляться через один раздел программного кода. Первоначальный вариант функций доступа может иметь следующий вид:

class classname {

var $attribute;

function __get($name) {

return $this->$name;

}

function __set($name, $value) {

$this->$name = $value;

}

}

Этот код предоставляет минимальные функции для доступа к атрибуту по имени $attribute. Функция __get() просто возвращает значение атрибута $attribute, а функция __set() присваивает ему новое значение.

Обратите внимание, что функция __get() принимает один параметр – имя атрибута – и возвращает значение этого атрибута. Аналогично, функция __set() принимает два параметра – имя атрибута и новое значение, которое должно быть ему присвоено.

Эти функции не вызываются напрямую. Два символа подчеркивания в начале имен функций означают, что они имеют в РНР специальное назначение подобно функциям __construct() и __destruct().

Рассмотрим, как работают эти функции. Если вы создаете экземпляр класса:

$а = new classname ();

то можете затем использовать функции __get() и __set() для проверки и установки значения любого атрибута.

Оператор

$a->$attribute = 5;

приведет к неявному вызову функции __set() с передачей ей значения "attribute" в параметре $name и 5 значения в параметре $value. Внутри функции __set() могут находиться необходимые проверки на ошибочные значения.

Функция __get() работает аналогичным образом. Если где-то в коде присутствует ссылка на атрибут:

$a->$attribute

то осуществляется неявный вызов функции __get() с передачей ей значения "attribute" в параметре $name. Это приведет к возврату функцией __get() значения атрибута.

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

При наличии только одной точки доступа можно организовать проверку на допустимость, дабы гарантировать, что сохраняются лишь данные, имеющие смысл. Если впоследствии выяснится, что значение атрибута $attribute может лежать только в диапазоне между 0 и 100, можно только один раз добавить несколько строк кода и реализовать проверку перед тем, как разрешить изменение. Теперь функции __set() можно придать такой вид:

function __set($name, $value) {

if ( $name="attribute" && $value >= 0 && $value <= 100 )

$this->attribute = $value;

}

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

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

Управление доступом с помощью модификаторов private и public

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

• Модификатор доступа public (общедоступный) устанавливается по умолчанию, т.е. подразумевается при отсутствии модификатора доступа. Такие элементы доступны как изнутри, так и извне класса.

• Модификатор доступа private (приватный) означает, что помеченный им элемент может быть доступен только изнутри класса. Его можно применять для всех атрибутов, если не используются функции доступа __get() и __set(). В отношении методов данный модификатор указывается, если тот или иной метод является служебным и предназначенным только для внутренних целей класса. Приватные элементы не наследуются (более подробно о наследовании рассказывается далее).

• Модификатор доступа protected (защищенный) означает, что помеченный им элемент может быть доступен только изнутри класса. Он также существует во всех подклассах; вопросы, связанные с его использованием, рассматриваются ниже в данной главе. Сейчас protected можно воспринимать как нечто среднее между public и private.

Добавьте в код класса, рассмотренного выше в качестве примера, модификаторы доступа:

class classname {

public $attribute;

public function __get($name) {

return $this->$name;

}

public function __set ($name, $value) {

$this->$name = $value;

}

}

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

Вызов операций класса

Операции класса можно вызывать в основном тем же способом, каким вызываются атрибуты класса. Если в нашем распоряжении имеется следующий класс:

class classname {

function operation1() {

}

function operation2($param1, $param2) {

}

}

и мы создаем объект типа classname с именем следующим образом:

$а = new classname();

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

$a->operation1() ;

$a->operation2(12, "test");

Если операции что-то возвращают, то возвращаемые данные можно получить следующим образом:

$х = $a->operation1();

$у = $a->operation2(12, "test");

Реализация наследования в РНР

Если класс должен быть подклассом другого класса, для указания этого факта используется ключевое слово extends. Приведенный ниже код создает класс В, унаследованный от ранее определенного класса А.

class В extends А {

public $attribute2;

function operation2 () {

}

}

Если класс А объявлен следующим образом:

class А {

public $attribute1;

function operation1() {

}

}

то все показанные ниже обращения к операциям и атрибутам объекта типа В будут допустимыми:

$b = new В () ;

$b->operation1 ();

$b->attribute1 = 10;

$b->operation2 ();

$b->attribute2 = 10;

Обратите внимание, что поскольку класс В является расширением класса А, мы можем ссылаться на операцию operation1() и атрибут $attribute1, несмотря на то, что они были объявлены в классе А. Будучи подклассом класса А, класс В обладает всей функциональностью и данными класса А. Наряду с этим, класс В объявляет свой собственный атрибут и операцию.

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

$а = new А() ;

$a->operation1();

$a->attribute1 = 10;

$a->operation2();

$a->attribute2 = 10;

Класс А не имеет ни операции operation2, ни атрибута attribute2.

Управление видимостью при наследовании с помощью модификаторов private и protected

Модификаторы доступа private и protected могут использоваться для управления наследованием. Если атрибут или метод определен как private, он не наследуется. Если же атрибут или метод определен как protected, он наследуется, однако невидим извне класса (подобно элементу private).

Рассмотрим следующий пример:

class А {

private function operation1() {

echo "Вызвана операция operation1";

}

protected function operation2() {

echo "Вызвана операция operation2";

}

public function operation3() {

echo "Вызвана операция operation3";

}

}

class В extends A {

function __construct() {

$this->operation1();

$this->operation2();

$this->operation3();

}

}

$b = new B;

Этот код создает в классе А по одной операции каждого типа: public, protected и private. Класс В наследуется от класса А. В рамках конструктора класса В предпринимаются попытки обратиться к операциям родительского класса.

Строка

$this->operation1();

приводит к следующей фатальной ошибке:

Fatal error: Call to private method A::operation1() from context 'B'

Фатальная ошибка: Вызов приватного метода А::operation1() из контекста 'В'

Этот пример показывает, что приватные операции, определенные в родительском классе, нельзя вызывать из дочернего класса.

Если закомментировать эту строку кода, остальные две будут работать нормально. Функция с модификатором protected наследуется, однако может использоваться только внутри дочернего класса, что, собственно, и сделано. В результате добавления следующей строки:

$b->operation2();

в конец файла, будет сгенерирована такая ошибка:

Fatal error: Call to protected method A: :operation2() from context ' '

Фатальная ошибка: Вызов защищенного метода A: :operation2() из контекста ' '

Вместе с тем, операцию operation3() можно вызывать и за пределами класса:

$b->operation3();

Данный вызов возможен, поскольку operation3() объявлена как public.

Переопределение

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

Рассмотрим, например, класс А со следующим определением:

class А {

public $attribute = "значение по умолчанию";

function operation () {

echo "Что-то здесь такое<br />";

echo "Значением \$attribute является ".$this->attribute."<br />";

}

}

Необходимо изменить значение атрибута $attribute, назначенное по умолчанию, и наделить операции operation() новыми функциональными возможностями. Для этого можно создать показанный ниже класс В, в котором переопределяются атрибут $attribute и операция operation():

class В extends А {

public $attribute = "другое значение";

function operation() {

echo "А здесь что-то другое<br />";

echo "Значением \$attribute является".$this->attribute."<br />";

}

}

Объявление класса В не влияет на исходное определение класса А. Рассмотрим следующие две строки кода:

$а = new А() ;

$а -> operation();

Эти строки создают объект типа А и вызывают его функцию operation(). В результате получается следующий вывод:

Что-то здесь такое

Значением $attribute является значение по умолчанию

доказывающий отсутствие влияния класса В на класс А. Создав объект типа В, получим другой вывод.

Следующий код:

$b = new В() ;

$b -> operation();

генерирует такой вывод:

А здесь что-то другое

Значением $attribute является другое значение

Как объявление новых атрибутов или операций в подклассе не оказывает влияния на суперкласс, так и переопределение атрибутов или операций в подклассе не оказывает влияния на суперкласс.

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

Ключевое слово parent позволяет обращаться к исходной версии операции в родительском классе. Например, для вызова А: : operation() внутри класса В применяется следующий код:

parent::operation() ;

Вывод, однако, будет другим. Несмотря на то что вызывается операция родительского класса, РНР использует значения атрибутов из текущего класса. Таким образом, вывод получается следующим:

Что-то здесь такое

Значением $attribute является другое значение

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

Предотвращение наследования и переопределения с помощью final

В РНР имеется ключевое слово final. Когда это слово используется перед объявлением функции, эта функция не может быть переопределена ни в одном подклассе.

Чтобы проиллюстрировать это, добавим final в код класса А из предыдущего примера:

class А {

public $attribute = 'значение по умолчанию';

final function operation() {

echo 'Что-то здесь такое<br />';

echo "Значением \$attribute является".$this->attribute."<br />";

}

}

В результате переопределять operation() в классе В уже будет нельзя. Если вы попытаетесь это сделать, то получите ошибку следующего вида:

Fatal error: Cannot override final method A::operation()

Фатальная ошибка: Невозможно переопределить финальный метод А::operation()

Можно также запретить создавать подклассы на основе заданного класса, поместив ключевое слово final перед определением класса, например:

final class А

{ ... }

При попытке унаследовать класс от А будет генерироваться ошибка:

Fatal error: Class В may not inherit from final class (A)

Фатальная ошибка: Класс В не может быть унаследован от финального класса (А)

Множественное наследование

В некоторых объектно-ориентированных языках (например, C++ и Smalltalk) допускается множественное наследование, однако РНР его не поддерживает. Это означает, что каждый класс может наследовать характеристики только от одного родительского класса. Количество дочерних классов, имеющих общий родительский класс, не ограничивается.

Вероятно, сказанное будет понятно не сразу. На рис. 6.1 показаны три возможных способа наследования для трех классов А, В и С.

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

Комбинация слева на рис. 6.1 показывает, что класс С наследуется от класса В, который, в свою очередь, наследуется от класса А. Каждый класс имеет не более одного родительского класса, следовательно, это вполне допустимое в РНР одиночное наследование.

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

Комбинация справа показывает, что класс С унаследован от двух классов А и В. В этом случае класс С имеет два родительских класса, следовательно, это множественное наследование, которое в РНР не поддерживается.

Реализация интерфейсов

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

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

interface Displayable {

function display();

}

class webPage implements Displayable {

function display() {

// ...

}

}

Этот пример демонстрирует обходной путь реализации множественного наследования, поскольку класс webPage может быть унаследован от одного класса и реализовывать один или более интерфейсов.

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

Проектирование классов

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

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

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

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

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

Для целей данного примера мы создадим класс страницы по имени Page. Основное назначение этого класса – ограничить объем HTML-кода, требуемого для создания новой страницы. Он должен допускать модификацию тех частей, которые изменяются от страницы к странице, и обеспечивать автоматическую генерацию тех фрагментов, которые остаются неизменными на каждой странице. Класс должен предоставлять гибкую структуру для построения новых страниц, но при этом не должен ограничивать свободу действий.

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

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

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

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

• Заменять стандартные элементы на конкретных страницах. Например, если в определенных разделах сайта требуются другие навигационные кнопки, мы должны иметь возможность заменять ими стандартные кнопки.