Классы и объекты в РНР со Штирлицом и Мюллером

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

Мы раскроем нашу тему на примере небольшого скрипта, который называется "Штирлиц и Мюллер". У Штирлица и Мюллера на двоих имеется 0.7 литра шнапса и 0.5 литра водки. Но Мюллер является немецким офицером и может пить только шнапс, тогда как Штирлиц помимо того, что он немецкий офицер, одновременно ещё и является советским разведчиком майором Исаевым, поэтому способен пить не только шнапс, но и водку. С каждым нажатием кнопки один из них выпивает 100 грамм того или иного напитка. А теперь зайдите сюда и сделайте так, чтобы из имеющихся 1.2 литра спиртного Штирлиц выпил как минимум 0.9 литра. Но не забывайте и про Мюллера.

А теперь посмотрим как работает этот скрипт.
Вот его основной код:

<?
заголовок();

$Мюллер = new Немецкий_Офицер;
$Штирлиц = new Советский_Разведчик;

$Мюллер->имя = "Мюллер";
$Штирлиц->имя = "Штирлиц";
$Штирлиц->настоящее_имя = "майор Исаев";

extract($_POST);
статистика();

if(
$Желание_Мюллера =='Хочу шнапса!') {
   
$Мюллер->Выпить_Шнапса($Шнапс);
   
$Мюллер->Самочувствие();
}
if(
$Желание_Штирлица =='Хочу шнапса!') {
   
$Штирлиц->Выпить_Шнапса($Шнапс);
   
$Штирлиц->Самочувствие();
}
if(
$Желание_Штирлица =='Хочу водки!') {
   
$Штирлиц->Выпить_Водки($Водка);
   
$Штирлиц->Самочувствие();
}

$Выпитое_Горючее = $Мюллер->Выпитый_Шнапс."|".$Штирлиц->Выпитый_Шнапс."|".$Штирлиц->Выпитая_Водка;

$Мюллер->Желания();
$Штирлиц->Желания();

выпитое($Штирлиц,$Мюллер);
запасы($Шнапс, $Водка);

окончание();

exit;

Одна из прелестей PHP в том, что код можно писать не только на английском, но и на многих других языках, в том числе и на русском, что вы и видите в данном примере. ( Имена переменных, функций, классов, объектов и т.д. в PHP могут состоять не только из латинских букв, но также могут содержать знак подчеркивания _, цифры (но не должны начинаться с цифр) и любие символы из расширенного набора ASCII ( ASCII-коды от 127 до 255 (0x7f-0xff) ), куда входят и хорошо нам знакомые значки кириллицы )

Итак, начнём с начала. <? - это начало любого PHP-кода, а ?> - его конец. Всё, что находится между этими значками, сервер обрабатывает как программу на PHP, а все что за ними - как простой HTML.

Далее идёт функция заголовок().

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

В этом скрипте несколько функций:

<?
заголовок();

extract($_POST);
статистика();

$Мюллер->Выпить_Шнапса($Шнапс);
$Мюллер->Самочувствие();
$Штирлиц->Выпить_Шнапса($Шнапс);
$Штирлиц->Самочувствие();
$Штирлиц->Выпить_Водки($Водка);
$Мюллер->Желания();
$Штирлиц->Желания();

выпитое($Штирлиц,$Мюллер);
запасы($Шнапс, $Водка);

окончание();

Одна из них - встроенная ( это - extract() ), а остальные - написанные пользователем (User-defined functions).
Встроенные функции написаны на Си и находятся в самом коде PHP, подробное описание каждой из них вы можете прочитать в PHP Manual. А для того чтобы заработали user-defined функции их надо сначала описать (обычно в конце программы или в отдельном файле). Делается это так:

function заголовок() {
    echo
"<HTML><HEAD><TITLE>Штирлиц и Мюллер</TITLE><meta content=\"text/html ; CHARSET=windows-1251\" http-equiv=\"Content-Type\"></HEAD><BODY>";
}

echo - это встроенная функция. Она выводит текст который стоит после неё в кавычках. Если в самом выводимом тексте есть кавычки то перед каждой из них надо кинуть вот такую палочку - \

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

А вот теперь мы и подошли к самому интересному, к нашей основной теме "Классы и Объекты".
Если перевести этот код

$Мюллер = new Немецкий_Офицер;
$Штирлиц = new Советский_Разведчик;

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

$Мюллер - новый объект класса Немецкий_Офицер
$Штирлиц
- новый объект класса Советский_Разведчик

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

$Борман = new Немецкий_Офицер;
$Шелленберг = new Немецкий_Офицер;
$Радистка_Кэт = new Советский_Разведчик;

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

Посмотрим, какими же функциями обладает каждый Немецкий_Офицер:

class Немецкий_Офицер {

    function
Немецкий_Офицер () {
       
$this->Острая_Необходимость = "Хочу шнапса!";
    }

    function
Желания() {
        global
$Шнапс, $Водка, $Выпитое_Горючее;
        echo
"Я - ".$this->имя.".
        <FORM METHOD=POST>
            <INPUT TYPE=hidden name=Шнапс value=$Шнапс>
            <INPUT TYPE=hidden name=Водка value=$Водка>
            <INPUT TYPE=hidden name=Выпитое_Горючее value='$Выпитое_Горючее'>
            <INPUT TYPE=submit name=Желание_"
.$this->имя."а value='".$this->Острая_Необходимость."'>
        </FORM><BR>"
;
    }

    function
Выпить_Шнапса($Шнапс) {
        global
$Шнапс;
        if(
$Шнапс != 0) {
            
$Шнапс=$Шнапс - 0.1;
            
$this->Выпитый_Шнапс = $this->Выпитый_Шнапс + 0.1;
            echo
$this->имя." только что выпил 100 грамм шнапса.<BR>Шнапса осталось $Шнапс литра.<BR>";
        } else {
            echo
"Шнапс кончился.<BR>";
        }
    }
    function
Самочувствие() {
        if(
$this->Выпитый_Шнапс + $this->Выпитая_Водка > 0.8) {
            echo
$this->имя." выпил ".$this->Выпитый_Шнапс." литра шнапса и ".$this->Выпитая_Водка." литра водки.<BR>";
            echo
$this->имя." склонился над картой Советского Союза.<BR>";
            echo
"Его неудержимо рвёт на Родину.<BR>";
            
окончание();
            exit;
        } elseif(
$this->Выпитый_Шнапс or $this->Выпитая_Водка) {
            echo
$this->имя."у хорошо. Но он хочет ещё.<BR><BR>";
        }
    }
}

Как видим, он может рассказать нам о своих Желаниях(), Выпить_Шнапса() и поведать о своем Самочувствии() после выпитого.

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

А теперь посмотрим, что может Советский_Разведчик

class Советский_Разведчик extends Немецкий_Офицер {

    function
Советский_Разведчик () {
       
$this->Острая_Необходимость = "Хочу шнапса!";
       
$this->Срочная_Необходимость = "Хочу водки!";
    }

    function
Желания() {
        global
$Шнапс, $Водка, $Выпитое_Горючее;
       
Немецкий_Офицер::Желания();
        echo
"Я - ".$this->настоящее_имя.".
        <FORM METHOD=POST>
            <INPUT TYPE=hidden name=Шнапс value=$Шнапс>
            <INPUT TYPE=hidden name=Водка value=$Водка>
            <INPUT TYPE=hidden name=Выпитое_Горючее value='$Выпитое_Горючее'>
            <INPUT TYPE=submit name=Желание_"
.$this->имя."а value='".$this->Срочная_Необходимость."'>
        </FORM><BR>"
;
    }

    function
Выпить_Водки($Водка) {
        global
$Водка;
        if(
$Водка != 0) {
            
$Водка=$Водка-0.1;
            
$this->Выпитая_Водка = $this->Выпитая_Водка + 0.1;
            echo
$this->имя." только что выпил 100 грамм водки.<BR>Водки осталось $Водка литра.<BR>";
        } else {
            echo
"Водка кончилась.<BR>";
        }
    }
}

Обратите внимание на первую строчку:

class Советский_Разведчик extends Немецкий_Офицер {

В переводе на русский это значит, что класс Советский_Разведчик расширяет класс Немецкий_Офицер. То есть, каждый заброшенный в тыл врага Советский_Разведчик является одновременно Немецким_Офицером. Поэтому ему доступны все функции Немецкого_Офицера - рассказать нам о своих Желаниях(), Выпить_Шнапса() и поведать о своем Самочувствии(), но также он может совершить действие недоступное ни одному Немецкому_Офицеру - Выпить_Водки()!!!

Это отражается и на его конструкторе. Как видим, каждый уважающий себя Советский_Разведчик при появлении на свет испытывает не только естественную для Немецкого_Офицера Острую_Необходимость Выпить_Шнапса, но также и Срочную_Необходимость Выпить_Водки без которой не может существовать ни один истинный Советский_Разведчик.

Эта ситуация, когда один класс (дочерний (child)) получает от другого (родительского (parent)) все его возможности, называется наследованием. Дочерний класс получает наследство от своего родителя, но при этом имеет вдобавок свои собственные способности.

Если одна и та же функция есть как у родительского,так и у дочернего классов (в данном случае Желания()) то дочернему классу одноименная функция родителя будет недоступна и он будет выполнять свою собственную. Но как же можно допустить чтобы доблестному Советскому_Разведчику могли быть недоступны какие-то функции Немецкого_Офицера? Для этих целей по рекомендации советской внешней разведки разработчиками PHP был введен специальный оператор ::

Теперь внутри одного класса можно вызвать функцию из другого класса написав следующее:

Немецкий_Офицер::Желания();

Так как Немецкий_Офицер является parent'ом для Советского_Разведчика, название класса Немецкий_Офицер можно также заменить ключевым словом parent:

parent::Желания();

Это приятное обстоятельство позволяет Советскому_Разведчику иметь желания как Советского_Разведчика (Выпить_Водки) так и Немецкого_Офицера (Выпить_Шнапса).

Итак, повторим еще раз, чтобы получше запомнить, класс - это набор переменных и функций, работающих с этими переменными. Любая переменная данного класса внутри описания этого класса должна начинаться с ключегого слова $this после которого следует знак разделения -> и только потом собственно имя переменной. $this (в переводе на русский - "этот") ставится вместо названия объекта над которым производится действие. Например, при выполнении функции Выпить_Шнапса() над объектом Мюллер переменные изменятся следующим образом:

Сначала мы создаем объект Мюллер и присваиваем переменной имя объекта Мюллер значение "Мюллер"

$Мюллер = new Немецкий_Офицер;
$Мюллер->имя = "Мюллер";
$Мюллер->Выпить_Шнапса($Шнапс);

а затем выполняем функцию Выпить_Шнапса()

[ function Выпить_Шнапса($Шнапс) {
        global
$Шнапс;
        if(
$Шнапс != 0) {
            
$Шнапс=$Шнапс - 0.1;
            
$this->Выпитый_Шнапс = $this->Выпитый_Шнапс + 0.1;
            echo
$this->имя." только что выпил 100 грамм шнапса.<BR>Шнапса осталось $Шнапс литра.<BR>";
        } else {
            echo
"Шнапс кончился.<BR>";
        }
    }
/
CODE]

При выполнении этой функции $this->имя будет соответствовать $Мюллер->имя и иметь значение "Мюллер".

Строка кода

echo $this->имя." только что выпил 100 грамм шнапса."

выведет на экран:

"Мюллер только что выпил 100 грамм шнапса."

После исполнения функции глобальная переменная $Шнапс (глобальная - потому что на неё влияют и Штирлиц и Мюллер, она рассматривается в глобальном аспекте, то есть не только внутри отдельного класса или функции, а в масштабе всего скрипта полностью) уменьшится на 0,1 литра. А переменная $Мюллер->Выпитый_Шнапс (принадлежащая только объекту $Мюллер и никому другому) увеличится на 0,1 литра.

Аналогично, когда ту же функцию будет вызывать объект $Штирлиц, слово $this будет заменено на $Штирлиц и те же самые действия будут произведены над переменными объекта $Штирлиц.

На этот раз строка кода

echo $this->имя." только что выпил 100 грамм шнапса."

выведет на экран:

"Штирлиц только что выпил 100 грамм шнапса."

Классы и объекты можно сравнить с деревом папок в файловой системе.

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

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

Так же как в файловой системе, где для того чтобы найти определённый файл, нужно указать полный путь к нему - C:/Чужие Документы/Классы/Немецкий_Офицер/Объекты/Штирлиц/Выпитый_Шнапс используя в качестве разделитеся значок / , точно так же и в PHP нужно указать полное имя функции или переменной, которую вы хотите вызвать. Здесь в качестве разделителя используется значок -> ( $Штирлиц->Выпитый_Шнапс , $Штирлиц->имя и т.д.).

Ну, вот в принципе и всё.

И, в заключение, полный исходник скрипта:

<?
заголовок();

$Мюллер = new Немецкий_Офицер;
$Штирлиц = new Советский_Разведчик;

$Мюллер->имя = "Мюллер";
$Штирлиц->имя = "Штирлиц";
$Штирлиц->настоящее_имя = "майор Исаев";

extract($_POST);
статистика();

if(
$Желание_Мюллера =='Хочу шнапса!') {
   
$Мюллер->Выпить_Шнапса($Шнапс);
   
$Мюллер->Самочувствие();
}
if(
$Желание_Штирлица =='Хочу шнапса!') {
   
$Штирлиц->Выпить_Шнапса($Шнапс);
   
$Штирлиц->Самочувствие();
}
if(
$Желание_Штирлица =='Хочу водки!') {
   
$Штирлиц->Выпить_Водки($Водка);
   
$Штирлиц->Самочувствие();
}

$Выпитое_Горючее = $Мюллер->Выпитый_Шнапс."|".$Штирлиц->Выпитый_Шнапс."|".$Штирлиц->Выпитая_Водка;

$Мюллер->Желания();
$Штирлиц->Желания();

выпитое($Штирлиц,$Мюллер);
запасы($Шнапс, $Водка);

окончание();

exit;


class
Немецкий_Офицер {

    function
Немецкий_Офицер () {
       
$this->Острая_Необходимость = "Хочу шнапса!";
    }

    function
Желания() {
        global
$Шнапс, $Водка, $Выпитое_Горючее;
        echo
"Я - ".$this->имя.".
        <FORM METHOD=POST ACTION="
.$_SERVER['PHP_SELF'].">
            <INPUT TYPE=hidden name=Шнапс value=$Шнапс>
            <INPUT TYPE=hidden name=Водка value=$Водка>
            <INPUT TYPE=hidden name=Выпитое_Горючее value='$Выпитое_Горючее'>
            <INPUT TYPE=submit name=Желание_"
.$this->имя."а value='".$this->Острая_Необходимость."'>
        </FORM><BR>"
;
    }

    function
Выпить_Шнапса($Шнапс) {
        global
$Шнапс;
        if(
$Шнапс != 0) {
            
$Шнапс=$Шнапс - 0.1;
            
$this->Выпитый_Шнапс = $this->Выпитый_Шнапс + 0.1;
            echo
$this->имя." только что выпил 100 грамм шнапса.<BR>Шнапса осталось $Шнапс литра.<BR>";
        } else {
            echo
"Шнапс кончился.<BR>";
        }
    }
    function
Самочувствие() {
        if(
$this->Выпитый_Шнапс + $this->Выпитая_Водка > 0.8) {
            echo
$this->имя." выпил ".$this->Выпитый_Шнапс." литра шнапса и ".$this->Выпитая_Водка." литра водки.<BR>";
            echo
$this->имя." склонился над картой Советского Союза.<BR>";
            echo
"Его неудержимо рвёт на Родину.<BR>";
            
окончание();
            exit;
        } elseif(
$this->Выпитый_Шнапс or $this->Выпитая_Водка) {
            echo
$this->имя."у хорошо. Но он хочет ещё.<BR><BR>";
        }
    }
}

class
Советский_Разведчик extends Немецкий_Офицер {

    function
Советский_Разведчик () {
       
$this->Острая_Необходимость = "Хочу шнапса!";
       
$this->Срочная_Необходимость = "Хочу водки!";
    }

    function
Желания() {
        global
$Шнапс, $Водка, $Выпитое_Горючее;
       
Немецкий_Офицер::Желания();
        echo
"Я - ".$this->настоящее_имя.".
        <FORM METHOD=POST ACTION="
.$_SERVER['PHP_SELF'].">
            <INPUT TYPE=hidden name=Шнапс value=$Шнапс>
            <INPUT TYPE=hidden name=Водка value=$Водка>
            <INPUT TYPE=hidden name=Выпитое_Горючее value='$Выпитое_Горючее'>
            <INPUT TYPE=submit name=Желание_"
.$this->имя."а value='".$this->Срочная_Необходимость."'>
        </FORM><BR>"
;
    }

    function
Выпить_Водки($Водка) {
        global
$Водка;
        if(
$Водка != 0) {
            
$Водка=$Водка-0.1;
            
$this->Выпитая_Водка = $this->Выпитая_Водка + 0.1;
            echo
$this->имя." только что выпил 100 грамм водки.<BR>Водки осталось $Водка литра.<BR>";
        } else {
            echo
"Водка кончилась.<BR>";
        }
    }
}

function
заголовок() {
    echo
"<HTML><HEAD><TITLE>Штирлиц и Мюллер</TITLE><meta content=\"text/html ; CHARSET=windows-1251\" http-equiv=\"Content-Type\"></HEAD><BODY>";
}
function
запасы($Шнапс, $Водка) {
    echo
"<H3>Запасы:</H3>Шнапс: $Шнапс<BR>Водка: $Водка<BR>";
}
function
выпитое($Штирлиц,$Мюллер) {
    echo
"<H3>Выпито:</H3>";
    if(!
$Мюллер->Выпитый_Шнапс) {
        echo
"Мюллер трезв.<BR>";
    } else {
        echo
"Мюллер выпил ".$Мюллер->Выпитый_Шнапс." литра шнапса.<BR>";
    }
    if(!
$Штирлиц->Выпитый_Шнапс and !$Штирлиц->Выпитая_Водка) {
        echo
"Штирлиц трезв.<BR>";
    } elseif(!
$Штирлиц->Выпитая_Водка) {
        echo
"Штирлиц выпил ".$Штирлиц->Выпитый_Шнапс." литра шнапса.<BR>";
    } elseif(!
$Штирлиц->Выпитый_Шнапс) {
        echo
"Штирлиц выпил ".$Штирлиц->Выпитая_Водка." литра водки.<BR>";
    } else {
        echo
"Штирлиц выпил ".$Штирлиц->Выпитый_Шнапс." литра шнапса и ".$Штирлиц->Выпитая_Водка." литра водки.<BR>";
    }
}

function
статистика() {
    global
$Шнапс, $Водка, $Штирлиц, $Мюллер, $Выпитое_Горючее;
    if(!isset(
$Водка)) {
       
$Водка = 0.5;
    }
    if(!isset(
$Шнапс)) {
       
$Шнапс = 0.7;
    }
   
$Выпитое_Горючее=explode ( "|", $Выпитое_Горючее);

   
$Мюллер->Выпитый_Шнапс = $Выпитое_Горючее[0];
   
$Штирлиц->Выпитый_Шнапс = $Выпитое_Горючее[1];
   
$Штирлиц->Выпитая_Водка = $Выпитое_Горючее[2];
}

function
окончание() {
    echo
"<BR><BR><BR><A HREF=http://www.apasov.com/php/schtirlitz_und_muller.php>Начать сначала</A><BR><BR><A HREF=http://www.apasov.com/>На главную страницу</A></BODY></HTML>";
}
?>


Автор: Илья Апасов