Лабораторная работа №5. Объекты и классы в PHP — КиберПедия 

Наброски и зарисовки растений, плодов, цветов: Освоить конструктивное построение структуры дерева через зарисовки отдельных деревьев, группы деревьев...

Индивидуальные очистные сооружения: К классу индивидуальных очистных сооружений относят сооружения, пропускная способность которых...

Лабораторная работа №5. Объекты и классы в PHP

2017-09-27 1168
Лабораторная работа №5. Объекты и классы в PHP 0.00 из 5.00 0 оценок
Заказать работу

Лабораторная работа №5. Объекты и классы в PHP

Класс как тип данных

До сих пор в программах мы оперировали переменными, хранящими значения определенного типа. В основном использовались типы string (строка) и douЫe (вещественное число), реже- array (ассоциативный массив). Для работы с такими переменными существует целый ряд операций: арифметические - для чисел; strlen (), substr () и т. д. - для строк; count (), array _merge () и др. - для массивов. ООП позволяет нам вводить новые типы данных в дополнение к уже существующим.

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

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

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

Рис. 1. Переменные объявляются при помощи типа, объекты - при помощи класса

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

Например, мы можем рассматривать тип int как класс. Тогда переменная этого "класса" будет обладать одним-единственным свойством (ее целым значением), а также набором методов (сложение, вычитание, инкремент и т. д.). При этом методы выглядят как арифметические операторы+,-,++ и т. д.

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

Альтернативное решение состоит в том, чтобы везде вместо + и других операций использовать вызовы соответствующих функций, например, add (), которые бы являлись методами класса. Собственно, только такой способ организации методов и поддерживается в РНР (а также в Java).

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

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

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

Давайте для тренировки опишем класс с именем мathComplex, объекты которого будут хранить комплексные числа (листинг 1). Этот класс пока поддерживает только сложение и вычитание чисел.

ПРИМЕЧАНИЕ

В математике комплексным числом называют пару двух вещественных чисел, первое из которых условно называют "действительной частью", а второе - "мнимой частью" комплексного числа. Все действительные числа соответствуют комплексным величинам с мнимой частью, равной нулю. Квадратный корень из -1, не существующий в виде действительного числа, имеет комплексное значение (0, 1), которое еще иногда обозначают знаком i. С комплексными числами можно выполнять все те же операции, что и с действительными - складывать, умножать, делить и т. д.

Листинг 1. Пример класс. Файл Math/Complex.php

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

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

Работа с классами

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

Доступ к свойствам объекта

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

Каждое свойство объекта доступно в программе по его имени. Можно присваивать значение свойству или получать его величину:

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

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

Доступ к методам

Вспомним, как мы вызывали "методы" встроенных типов данных:

Как видите, для встроенных типов используется либо операторная запись вызова "метода" (например, сложение), либо же функциональная (как будто вызывается функция). В РНР для вызова метода некоторого объекта используется оператор "стрелка".

Листинг 2 Вызов метода объекта. Файл call.php

Давайте посмотрим, что происходит, когда мы вызываем метод класса. Первым делом создается локальная переменная $this, которой присваивается то же значение, что было у $obj. То есть, в $this теперь хранится ссылка на объект, для которого вызывается метод. Далее РНР смотрит, какому классу принадлежит $obj (в нашем случае это MathCornplex), и находит функцию-член: MathCornplex:: add (). Функция вызывается, при этом $this, напомним, равен $obj. В итоге add() изменяет значения $obj->re и $obj->im (которые для нее выглядят как $this->re и $this->im. Их мы распечатываем следующей строчкой программы, уже после выхода из функции.

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

ПРИМЕЧАНИЕ

В отличие от таких языков, как С++ и Java, в РНР не поддерживается создание в одном классе нескольких методов с одинаковым именем, которые бы различались только типами и количеством аргументов. Поэтому-то нам и пришлось создавать класс MathComplex1, а не просто добавить новую функцию add () с аргументом типа MathComplex в имеющийся класс.

Перегрузка преобразования в строку

Посмотрите еще раз на листинг 3. Возможно, вы спросите: почему мы назвали функцию _tostring() столь длинным именем? И зачем эти неуклюжие символы подчеркивания?

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

Листинг 5 Перегрузка интерполяции. Файл tostring.php

Обратите внимание, что мы вставляем объект $а прямо в строку, и в момент интерполяции переменных РНР вызывает метод __tostring (). Результат будет таким:

Значение: (314, 101)

Если бы не метод __toString () (например, при использовании класса MathComplex, который мы написали в самом начале этой главы), вывод был бы другим:

Catchable fatal error: Object of class MathComplex could not bе converted to string

Как видите, РНР генерирует ошибку, в которой сообщает о невозможности преобразования объекта класса МathComplex в строку.

Инициализация и разрушение

Давайте еще раз взглянем на листинги 4 и 5. Как видите, для корректного создания объекта нам недостаточно просто использовать оператор new: потом приходится еще инициализировать свойства объекта ($re и $im). Конечно, это утомительно, и о присваивании легко случайно позабыть, - в результате будет ошибка. В нашем примере инициализация очень проста, однако в реальной ситуации она может быть, наоборот, весьма объемна (например, если класс требует загрузки каких-нибудь файлов или записей из базы данных).

Конструктор

Давайте взглянем на очередную реализацию нашего класса комплексных чисел (листинг 6).

Листинг 6 Пример класса с конструктором. Файл Math/Complex2.php

Обратите внимание на необычное название метода - _ construct (). Это так называемый конструктор класса. Он вызывается всякий раз, когда вы используете оператор new для объекта.

ПРИМЕЧАНИЕ

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

Как видите, конструктор принимает два параметра: действительную и вещественную части комплексного числа. Листинг 7 иллюстрирует применение данного класса.

Листинг 7 Использование конструктора. Файл construct.php

Насколько легче стало создание новых объектов! Теперь мы уже при всем желании не сможем пропустить их инициализацию - конструктор будет вызван в любом случае.

Параметры по умолчанию

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

Мы заставим РНР корректно воспринимать следующие четыре команды:

При этом недостающие параметры будут заполнены значениями по умолчанию (в нашем примере это 0).

В примере, который только что был приведен, по умолчанию создается объект класса MathCornplex2 со значением (0, 0). В языках программирования вроде Java и С++ конструктор класса, который допускает создание объектов без указания параметров, называется конструктором по умолчанию.

Деструктор

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

Описание деструктора

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

Деструктор - это специальный метод класса с именем _ destruct (), который будет гарантированно вызван при потере последней ссылки на объект в программе. Так как деструктор запускается самим РНР, он не должен принимать никаких параметров.

В листинге 11 приведен модифицированный класс с именем FileLogger, в котором объявляется деструктор. Теперь нам уже нет необходимости заботиться о "ручном" вызове close () в программе - РНР выполняет "финализирующие" действия самостоятельно.

Листинг 11 Деструктор. Файл File/Logger.php

Листинг 12 Использование класса с деструктором. Файл destr.php

Посмотрите на листинг 12. В нем мы последовательно создаем 10 объектов класса FileLogger, полагаясь на то, что их деструкторы будут вызваны в нужное время. Зададимся важным вопросом: в какой именно момент это произойдет? Интуитивно понятно, что деструктор вызывается в тот момент, когда объект в программе больше не нужен, и память, отведенную под него, можно освободить. Это событие в нашем случае происходит при перезаписи переменной $logger, т. е. когда ей присваивается новое значение (первая команда цикла).

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

Если вы модифицируете программу таким образом, то обнаружите, что она попрежнему работает! Записи в lоg-файл test.log добавляются, причем их очередность остается той же, что была ранее. Но в какой же момент РНР вызывает деструкторы десяти объектов в этом случае?

Алгоритм сбора мусора

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

ПРИМЕЧАНИЕ

Представьте, что объект - это пальто, сданное в гардероб. Тогда в качестве ссылки будет выступать номерок на это пальто, выдаваемый гардеробщиком. Этот номерок можно "копировать" - например, отдав в мастерскую (аналог присваивания переменных). При этом пальто остается тем же самым и не изменяется. Что произойдет с пальто, если человек уничтожит свой номерок (обнулит ссылку на объект)?.. Наверное, через некоторое время гардеробщик сообразит, что пальто больше не нужно и лишь занимает вешалку, и отправит его на утилизацию - диспетчер динамической памяти (или, как его еще называют, сборщик мусора) удалит объект-пальто. Однако РНР гораздо "шустрее": если гардеробщику требуется некоторое время на принятие решения, то интерпретатор сразу же обнаруживает объекты, на которые нет ссылок, и удаляет их, не задерживаясь.

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

В этом и заключена специфика алгоритма со счетчиком ссылок, применяемого в РНР (а также в Perl), одновременно его сила и слабость. Любой объект, который вы создаете, содержит в себе скрытое поле, хранящее так называемый счетчик ссылок. Каждый раз, когда в программе появляется новая ссылка на объект, этот счетчик увеличивается на 1 (обычно это происходит при выполнении операции присваивания $alias = $source: раньше ссылка хранилась только в $source, а теперь и в $alias, и в $source). Соответственно, при удалении ссылки счетчик уменьшается на 1. Например, операция unset ($alias), $alias - "что угодно", а также выход локальной переменной функции за область видимости приводит к потере ссылки на объект, которая раньше находилась в $alias. Ясно, что при обнулении счетчика на объект больше никто не ссылается, а потому его можно спокойно удалить из памяти, что РНР и делает. Таким образом, объект удаляется после некоторой операции присваивания, приводящей к потере последней ссылки на него.

Удаление объекта или массива - довольно сложная процедура. Интерпретатору необходимо:

· удалить все ссылки, которые содержит сам этот объект (например, при удалении массива нужно обнулить все элементы, которые в нем содержатся - на случай, если они сами являются объектами). Если в процессе этой операции какой-то другой подчиненный объект теряет последнюю ссылку, то он также будет удален, и т. д. -рекурсивно;

· вызвать деструктор; деструкторы играют весьма важную роль в ООП, так что полная их поддержка в алгоритме со счетчиком ссылок - это сильная сторона метода;

· освободить занимаемую память; эта операция выполняется в самый последний момент и может рассматриваться как низкоуровневая.

Циклические ссылки

Алгоритмы сборки мусора с использованием счетчика ссылок, как правило, имеют один очень существенный недостаток. Речь идет о циклических ссылках. Давайте рассмотрим пример (листинг 13).

Листинг 13 Проблемы алгоритма со счетчиком ссылок. Файл refcount.php

В программе создаются два объекта: $father и $child. При этом объект-отец хранит ссылки на всех своих потомков, а каждый сын - ссылку на отца. Это и называется циклическими ссылками: если идти "вдоль них", мы никогда не остановимся. Цикличе­ские ссылки встречаются на практике очень часто, особенно при описании иерархических структур.

Теперь взгляните на предпоследнюю строчку кода. Мы присваиваем ссылочным переменным $father и $child значение NULL, в результате чего счетчик ссылок в соответствующих объектах уменьшается на 1.

А теперь - "сюрприз": несмотря на то, что в программе мы уже никак не сможем "добраться" до данных объектов $father и $child (мы же уничтожили эти ссылки), память для них все же не освобождается, и они остаются "висеть" мертвым грузом, хотя к ним уже и нельзя получить доступ! Убедиться в этом можно, запустив скрипт листинга 13 в браузере:

Пока что все живы... Убиваем всех.

все умерли, конец программы.

Father умер.

Child умер.

Как видите, сообщение "Все умерли", выводящееся в конце программы, оказывается самым первым, а не последним по списку. Это означает, что деструкторы были вызваны уже после завершения работы скрипта.

Давайте теперь в качестве эксперимента уберем строчку: $father->children[] = $child. Таким образом, теперь в программе уже не будет кольцевых ссылок, и результат ее работы станет выглядеть так:

Пока что все живы... Убиваем всех.

Child умер.

Father умер.

Все умерли, конец программы.

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

Проблема циклических ссылок

Все дело в злополучных счетчиках ссылок. Смотрите: $father ссылается на $child, а $child - на $father. Это значит, что и у того, и у другого счетчик равен 1 (ведь на каждого из них ссылается другой)! Стоит ли удивляться, что сборщик мусора не сработал?.. Ведь в программе не осталось ни одного объекта с нулевым счетчиком ссылок.

ПРИМЕЧАНИЕ

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

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

ПРИМЕЧАНИЕ

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

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

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

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

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

Вкратце, все объекты, генерирующие ссылки, помещаются в специальный буфер, который называется корневым. При заполнении буфера (а его размер составляет 10000) стартует процедура сборки мусора, в результате которой происходит обход дерева всех ссылающихся элементов, алгоритм разрешает циклы и корректирует счетчики. Объекты, чьи счетчики стали равны нулю, удаляются. Механизм довольно ресурсоемок и включается только по заполнению буфера. По умолчанию сборщик мусора включен; если ваши скрипты работают короткое время и потребляют мало памяти, можно увеличить производительность за счет отключения сборщика мусора, установив значение директивы\ zend.enable_gc в конфигурационном файле php.ini в off.


Задания для самостоятельного решения

Лабораторная работа №5. Объекты и классы в PHP

Класс как тип данных

До сих пор в программах мы оперировали переменными, хранящими значения определенного типа. В основном использовались типы string (строка) и douЫe (вещественное число), реже- array (ассоциативный массив). Для работы с такими переменными существует целый ряд операций: арифметические - для чисел; strlen (), substr () и т. д. - для строк; count (), array _merge () и др. - для массивов. ООП позволяет нам вводить новые типы данных в дополнение к уже существующим.

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

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

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

Рис. 1. Переменные объявляются при помощи типа, объекты - при помощи класса

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

Например, мы можем рассматривать тип int как класс. Тогда переменная этого "класса" будет обладать одним-единственным свойством (ее целым значением), а также набором методов (сложение, вычитание, инкремент и т. д.). При этом методы выглядят как арифметические операторы+,-,++ и т. д.

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

Альтернативное решение состоит в том, чтобы везде вместо + и других операций использовать вызовы соответствующих функций, например, add (), которые бы являлись методами класса. Собственно, только такой способ организации методов и поддерживается в РНР (а также в Java).

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

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

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

Давайте для тренировки опишем класс с именем мathComplex, объекты которого будут хранить комплексные числа (листинг 1). Этот класс пока поддерживает только сложение и вычитание чисел.

ПРИМЕЧАНИЕ

В математике комплексным числом называют пару двух вещественных чисел, первое из которых условно называют "действительной частью", а второе - "мнимой частью" комплексного числа. Все действительные числа соответствуют комплексным величинам с мнимой частью, равной нулю. Квадратный корень из -1, не существующий в виде действительного числа, имеет комплексное значение (0, 1), которое еще иногда обозначают знаком i. С комплексными числами можно выполнять все те же операции, что и с действительными - складывать, умножать, делить и т. д.

Листинг 1. Пример класс. Файл Math/Complex.php

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

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

Работа с классами

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


Поделиться с друзьями:

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

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

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.096 с.