Object Oriented Programming для чайников

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

Если Вы проходили собеседования в крупные компании, то уже слышали о "трех столпах" ООП: полиморфизм, инкапсуляция и наследование. Давайте более детально поговорим о каждом из них и постараемся понять почему понимание этих терминов так важно для хорошего разработчика.

Для начала хотелось бы напомнить что элементы класса могут быть объявлены как:

public – открытые, или общедоступные;
protected – защищенные;
private – закрытые.

Полиморфизм

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

Источник wikipedia

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

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

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

Пример:

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

Отлично! Давайте представим себя в роли разработчика концепции телефона. Вчера вечером к Вам пришел начальник и потребовал к завтрашнему утру подготовить новую линейку телефонов для презентации. В линейке должно быть как минимум три телефона и они должны иметь камеру не менее 30MP.

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

<?php
use POphone; #Какая-то системная библиотека, которая реализует основные функции телефона
abstract class Telephone
{
   public $model;
    public $screensize;
    public $weight;
    public $camera = 30
    protected $imei;
    protected $serial_number;
    /**
     * Функция вызывается при создании экземпляра объекта
     */
    public function _constructor()
    {
     POphone::boot();
    }
    /**
     * Функция звонка
     * @param  integer $number номер телефона
     * @return POphone
     */
    public function call($number=0)
    {
        return POphone::call($number);
    }
    /**
     * Функция отправки смс
     * @param  integer $nubmer номер телефона
     * @param  integer $text   сообщение
     * @return POphone
     */
    public function sms($nubmer=0,$text=0)
    {
        return POphone::sendSMS($nubmer, $text);
    }
    /**
     * Выход в интернет
     * @param  string $url Адрес сайта
     * @return POphone
     */
    public function goEnternet($url="")
    {
        return POphone::openUrl($url);
    }
}

Превосходно! Скелет готов. Теперь у нас есть базовый класс телефона, который имеет методы: call для звонков, sms для отправки sms и goEnternet для выхода в интернет.  Так же базовый класс имеет свойства, которые должен иметь каждый из трех телефонов: model - модель, camera - разрешение камеры, weight - вес, imei, serial_number - серийный номер. Стоит заметить что такие свойства как imei и serial_number не должны быть доступны каждому желающему, поэтому мы ограничили к ним доступ, указав модификатор доступа protected. Разрешение камеры имеет значение по умолчанию, так как начальник попросил нас снабдить все телефоны камерой 30MP.

Теперь можно приступить к созданию трех телефонов, на базе нашего абстрактного скелета. Давайте создадим производный класс(телефон), модель которого будет "XY1", размер дисплея - 5 дюймов, вес - 150гр. Напишем класс "XY1", который посредством полиморфизма унаследует все возможности базовой концепции телефона  и добавит свои уникальные характеристики:

<?php
class XY1 extends Telephone {
    public function __construct()
    {
        $this->model = "XY1";
        $this->screensize = 5;
        $this->weight = 150;
        $this->imei = "NQBVOAB****FAQEQPVNA";
        $this->serial_number = 471019502312561920237;
        // вызываем конструктор родительского класса
        parent::__construct();
    }
}

Отлично! Ключевое слово "extends"  - указывает что класс "XY1" является производным классом от базового класса "Telephone" и он унаследовал все методы и свойства базовго класса. Поздравляю! Ваш XY1 готов! Он может звонить, отправлять sms и выходить в интернет. Теперь Вы знаете что такое полиморфизм! Но вот Вам следующая задача, "XY2" должен отправлять сообщения с шифрованием и иметь доступ к интернету через 5G. Давайте напишем класс, который не только наследует методы базового класса, но и изменяет их, а так же дополняет.

<?php
use POphone;
class XY2 extends Telephone {
    public function __construct()
    {
        $this->model = "XY2";
        $this->screensize = 8;
        $this->weight = 260;
        $this->imei = "NQSCAQB****FAQEQVAAA";
        $this->serial_number = 59179515781517951;
        // вызываем конструктор родительского класса
        parent::__construct();
    }

    /**
     * Функция отправки смс использованием вымышленного шифорвания. Важно заметить что в базовом классе уже есть этот метод и создав в производном классе метод с таким же именем, мы его переопределяем для экземпляров производного класса
     * @param  integer $nubmer номер телефона
     * @param  integer $text   сообщение
     * @return POphone
     */
    public function sms($nubmer=0,$text=0)
    {
        return POphone::md200hasHack(POphone::sendSMS($nubmer, $text));
    }

    /**
     * Выход в интернет через 5G! Важно заметить что этого метода не бло в базовом классе
     * @param  string $url адрес сайта
     * @return POphone
     */
    public function goEnternet5G($url="")
    {
        return POphone::openUrlWith5G($url);
    }
}

Инкапсуляция

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

<?php
class XY2 extends Telephone
{
    ...
    protected $imei;
    ...
    public function getImei()
    {
        return substr($this->imei, 1, 3);
    }
}
////////////////////////////////
$telephoneXY2 = new XY2;
echo $telephoneXY2->getImei(); // 591
echo $telephoneXY2->imei; // Error! Вы не имеете доступа к protected-свойствам

Таким образом мы защитили imei от записи и не дали возможность пользователю получить полный imei. Это и называется инкапсуляцией. Важно понимать что инкапсуляцией является не сам факт скрытия приватных данных, а способ их получения через публичный метод.

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

Все казалось бы отлично, но мы так и не сделали 3-ю модель для презентации, а на часах уже 6:00. И тут в голову приходит гениальная идея - создать XY3 на основе XY1, только в большим дисплеем! Давайте напишем класс XY3, который будет наследником класса XY1, но свойство screensize будет иметь большее значение.

 

<?php
use XY1;
class XY3 extends XY1 {
    public function __construct()
    {
        // вызываем конструктор родительского класса
        parent::__construct();
        $this->screensize = 10; // Изменяем значение свойства screensize
    }
}

Таким образом XY3 получил все методы и свойства XY1, но имеет свойство screensize, отличное от XY1.

Поздравляю с выполнением задания! Теперь Ваш босс будет Вами гордиться!