Вступ до об'єктно-орієнтованого програмування
Термін „ООП“ означає об'єктно-орієнтоване програмування, що є способом організації та структурування коду. ООП дозволяє нам розглядати програму як набір об'єктів, що взаємодіють між собою, а не як послідовність команд і функцій.
В ООП „об'єкт“ – це одиниця, яка містить дані та функції, що працюють з цими даними. Об'єкти створюються за „класами“, які можна розуміти як креслення або шаблони для об'єктів. Коли ми маємо клас, ми можемо створити його „екземпляр“, що є конкретним об'єктом, створеним за цим класом.
Давайте покажемо, як ми можемо створити простий клас у PHP. При визначенні класу ми використовуємо ключове слово „class“, за яким слідує назва класу, а потім фігурні дужки, що оточують функції (їх називають „методами“) та змінні класу (їх називають „властивостями“ або англійською „property“):
У цьому прикладі ми створили клас з назвою Автомобіль
з однією
функцією (або „методом“), названою посигналити
.
Кожен клас повинен вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
Класи зазвичай зберігаються в окремих файлах, щоб код був
організованим і легким для навігації. Назва файлу повинна відповідати
назві класу, тому для класу Автомобіль
назва файлу буде
Автомобіль.php
.
При іменуванні класів добре дотримуватися конвенції „PascalCase“, що означає, що кожне слово в назві починається з великої літери, і між ними немає підкреслень або інших роздільників. Методи та властивості використовують конвенцію „camelCase“, тобто починаються з малої літери.
Деякі методи в PHP мають спеціальні завдання і позначаються префіксом
__
(два підкреслення). Одним з найважливіших спеціальних методів
є „конструктор“, який позначається як __construct
. Конструктор – це
метод, який автоматично викликається, коли ви створюєте новий
екземпляр класу.
Конструктор часто використовується для встановлення початкового стану об'єкта. Наприклад, коли ви створюєте об'єкт, що представляє особу, ви можете використати конструктор для встановлення її віку, імені або інших властивостей.
Давайте покажемо, як використовувати конструктор у PHP:
У цьому прикладі клас Людина
має властивість (змінну)
$вік
та конструктор, який встановлює цю властивість. Метод
отриматиВік()
потім дозволяє отримати доступ до віку особи.
Псевдозмінна $this
використовується всередині класу для
доступу до властивостей та методів об'єкта.
Ключове слово new
використовується для створення нового
екземпляра класу. У наведеному вище прикладі ми створили нову людину з
віком 25.
Ви також можете встановити значення за замовчуванням для параметрів конструктора, якщо вони не вказані під час створення об'єкта. Наприклад:
У цьому прикладі, якщо ви не вкажете вік при створенні об'єкта
Людина
, буде використано значення за замовчуванням 20.
Приємно, що визначення властивості з її ініціалізацією через конструктор можна так скоротити та спростити:
Для повноти, крім конструкторів, об'єкти можуть мати й деструктори
(метод __destruct
), які викликаються перед тим, як об'єкт буде
звільнений з пам'яті.
Простори імен
Простори імен (або „namespaces“ англійською) дозволяють нам організовувати та групувати пов'язані класи, функції та константи, а також уникати конфліктів імен. Ви можете уявити їх як папки на комп'ютері, де кожна папка містить файли, що належать до певного проекту або теми.
Простори імен особливо корисні у великих проектах або коли ви використовуєте бібліотеки сторонніх розробників, де можуть виникнути конфлікти в назвах класів.
Уявіть, що у вас є клас з назвою Автомобіль
у вашому проекті, і
ви хочете розмістити його в просторі імен під назвою Транспорт
.
Ви зробите це так:
Якщо ви хочете використати клас Автомобіль
в іншому файлі, ви
повинні вказати, з якого простору імен походить клас:
Для спрощення ви можете на початку файлу вказати, який клас з даного простору імен ви хочете використовувати, що дозволяє створювати екземпляри без необхідності вказувати повний шлях:
Успадкування
Успадкування є інструментом об'єктно-орієнтованого програмування, який дозволяє створювати нові класи на основі вже існуючих класів, переймати їхні властивості та методи, а також розширювати або перевизначати їх за потребою. Успадкування дозволяє забезпечити повторне використання коду та ієрархію класів.
Простіше кажучи, якщо ми маємо один клас і хотіли б створити інший, похідний від нього, але з деякими змінами, ми можемо „успадкувати“ новий клас від початкового класу.
У PHP успадкування реалізується за допомогою ключового слова
extends
.
Наш клас Людина
зберігає інформацію про вік. Ми можемо мати
інший клас Student
, який розширює Людину
і додає інформацію
про спеціальність.
Розглянемо приклад:
Як працює цей код?
- Ми використали ключове слово
extends
для розширення класуЛюдина
, що означає, що класStudent
успадкує всі методи та властивості відЛюдини
. - Ключове слово
parent::
дозволяє нам викликати методи з батьківського класу. У цьому випадку ми викликали конструктор з класуЛюдина
перед додаванням власної функціональності до класуStudent
. І аналогічно методвивестиІнформацію()
батьківського класу перед виведенням інформації про студента.
Успадкування призначене для ситуацій, коли існує відношення „є“ між
класами. Наприклад, Student
є Людина
. Кішка є твариною. Це дає
нам можливість у випадках, коли в коді ми очікуємо один об'єкт (напр.,
„Людина“), використати замість нього успадкований об'єкт (напр.,
„Student“).
Важливо усвідомити, що основною метою успадкування не є запобігання дублюванню коду. Навпаки, неправильне використання успадкування може призвести до складного і важкопідтримуваного коду. Якщо відношення „є“ між класами не існує, ми повинні замість успадкування розглянути композицію.
Зверніть увагу, що методи вивестиІнформацію()
в класах
Людина
та Student
виводять трохи різну інформацію. І ми
можемо додати інші класи (наприклад, Працівник
), які
надаватимуть інші реалізації цього методу. Здатність об'єктів різних
класів реагувати на один і той самий метод різними способами
називається поліморфізмом:
Композиція
Композиція – це техніка, коли замість того, щоб успадковувати властивості та методи іншого класу, ми просто використовуємо його екземпляр у нашому класі. Це дозволяє нам комбінувати функціональність та властивості кількох класів без необхідності створювати складні структури успадкування.
Розглянемо приклад. Ми маємо клас Двигун
і клас
Автомобіль
. Замість того, щоб говорити „Автомобіль є Двигун“,
ми говоримо „Автомобіль має Двигун“, що є типовим відношенням
композиції.
Тут Автомобіль
не має всіх властивостей та методів
Двигуна
, але має до нього доступ через властивість
$двигун
.
Перевагою композиції є більша гнучкість у дизайні та краща можливість модифікації в майбутньому.
Видимість
У PHP ви можете визначити „видимість“ для властивостей, методів та констант класу. Видимість визначає, звідки ви можете отримати доступ до цих елементів.
- Public: Якщо елемент позначений як
public
, це означає, що до нього можна отримати доступ звідусіль, навіть поза класом. - Protected: Елемент з позначкою
protected
доступний лише в межах даного класу та всіх його нащадків (класів, що успадковують від цього класу). - Private: Якщо елемент є
private
, до нього можна отримати доступ лише зсередини класу, в якому він був визначений.
Якщо ви не вкажете видимість, PHP автоматично встановить її на
public
.
Розглянемо приклад коду:
Продовжимо з успадкуванням класу:
У цьому випадку метод вивестиВластивості()
в класі
НащадокКласу
може отримати доступ до публічних та захищених
властивостей, але не може отримати доступ до приватних властивостей
батьківського класу.
Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
Ключове слово final
У PHP ми можемо використовувати ключове слово final
, якщо хочемо
заборонити класу, методу або константі успадковуватися або
перевизначатися. Коли ми позначаємо клас як final
, він не може бути
розширений. Коли ми позначаємо метод як final
, він не може бути
перевизначений у класі-нащадку.
Усвідомлення того, що певний клас або метод не буде далі модифікуватися, дозволяє нам легше вносити зміни, не побоюючись можливих конфліктів. Наприклад, ми можемо додати новий метод без побоювань, що якийсь його нащадок вже має метод з такою ж назвою і сталася б колізія. Або ми можемо змінити параметри методу, оскільки знову ж таки немає ризику викликати невідповідність з перевизначеним методом у нащадку.
У цьому прикладі спроба успадкування від фінального класу
ФінальнийКлас
викличе помилку.
Статичні властивості та методи
Коли ми говоримо в PHP про „статичні“ елементи класу, ми маємо на увазі методи та властивості, які належать самому класу, а не конкретному екземпляру цього класу. Це означає, що вам не потрібно створювати екземпляр класу, щоб отримати до них доступ. Замість цього ви викликаєте їх або отримуєте до них доступ безпосередньо через назву класу.
Майте на увазі, що оскільки статичні елементи належать класу, а не
його екземплярам, ви не можете всередині статичних методів
використовувати псевдозмінну $this
.
Використання статичних властивостей призводить до незрозумілого коду, повного підводних каменів, тому ви ніколи не повинні їх використовувати, і ми навіть не будемо показувати приклад використання тут. Навпаки, статичні методи корисні. Приклад використання:
У цьому прикладі ми створили клас Калькулятор
з двома
статичними методами. Ці методи ми можемо викликати безпосередньо без
створення екземпляра класу за допомогою оператора ::
. Статичні
методи особливо корисні для операцій, які не залежать від стану
конкретного екземпляра класу.
Константи класу
У межах класів ми маємо можливість визначати константи. Константи – це значення, які ніколи не змінюються під час виконання програми. На відміну від змінних, значення константи залишається незмінним.
У цьому прикладі ми маємо клас Автомобіль
з константою
КількістьКоліс
. Коли ми хочемо отримати доступ до константи
всередині класу, ми можемо використовувати ключове слово self
замість назви класу.
Інтерфейси об'єктів
Інтерфейси об'єктів діють як „контракти“ для класів. Якщо клас має реалізувати інтерфейс, він повинен містити всі методи, які визначає цей інтерфейс. Це чудовий спосіб забезпечити, щоб певні класи дотримувалися однакового „контракту“ або структури.
У PHP інтерфейс визначається ключовим словом interface
. Усі методи,
визначені в інтерфейсі, є публічними (public
). Коли клас реалізує
інтерфейс, він використовує ключове слово implements
.
Якщо клас реалізує інтерфейс, але в ньому не визначені всі очікувані методи, PHP видасть помилку.
Клас може реалізувати кілька інтерфейсів одночасно, що є відмінністю від успадкування, де клас може успадковувати лише від одного класу:
Абстрактні класи
Абстрактні класи служать базовими шаблонами для інших класів, але ви не можете створювати їхні екземпляри безпосередньо. Вони містять комбінацію повних методів та абстрактних методів, які не мають визначеного вмісту. Класи, що успадковують від абстрактних класів, повинні надати визначення для всіх абстрактних методів предка.
Для визначення абстрактного класу ми використовуємо ключове слово
abstract
.
У цьому прикладі ми маємо абстрактний клас з одним звичайним та одним
абстрактним методом. Потім ми маємо клас Нащадок
, який
успадковує від АбстрактнийКлас
і надає реалізацію для
абстрактного методу.
Чим насправді відрізняються інтерфейси та абстрактні класи? Абстрактні класи можуть містити як абстрактні, так і конкретні методи, тоді як інтерфейси лише визначають, які методи повинен реалізувати клас, але не надають жодної реалізації. Клас може успадковувати лише від одного абстрактного класу, але може реалізувати будь-яку кількість інтерфейсів.
Перевірка типів
У програмуванні дуже важливо мати впевненість, що дані, з якими ми працюємо, мають правильний тип. У PHP є інструменти, які нам це забезпечують. Перевірка того, чи мають дані правильний тип, називається „перевіркою типів“.
Типи, з якими ми можемо зіткнутися в PHP:
- Базові типи: Включають
int
(цілі числа),float
(дійсні числа),bool
(логічні значення),string
(рядки),array
(масиви) таnull
. - Класи: Якщо ми хочемо, щоб значення було екземпляром певного класу.
- Інтерфейси: Визначає набір методів, які клас повинен реалізувати. Значення, яке відповідає інтерфейсу, повинно мати ці методи.
- Змішані типи: Ми можемо вказати, що змінна може мати кілька дозволених типів.
- Void: Цей спеціальний тип позначає, що функція чи метод не повертає жодного значення.
Давайте покажемо, як змінити код, щоб він включав типи:
Таким чином ми забезпечили, що наш код очікує та працює з даними правильного типу, що допомагає нам запобігати потенційним помилкам.
Деякі типи неможливо записати безпосередньо в PHP. У такому випадку
вони вказуються в коментарі phpDoc, що є стандартним форматом для
документування коду PHP, який починається з /**
і закінчується
*/
. Він дозволяє додавати описи класів, методів тощо. А також
вказувати складні типи за допомогою так званих анотацій @var
,
@param
та @return
. Ці типи потім використовуються
інструментами статичного аналізу коду, але сам PHP їх не перевіряє.
Порівняння та ідентичність
У PHP ви можете порівнювати об'єкти двома способами:
- Порівняння значень
==
: Перевіряє, чи об'єкти належать до одного класу і мають однакові значення у своїх властивостях. - Ідентичність
===
: Перевіряє, чи це той самий екземпляр об'єкта.
Оператор instanceof
Оператор instanceof
дозволяє визначити, чи є даний об'єкт
екземпляром певного класу, нащадком цього класу, або чи реалізує він
певний інтерфейс.
Уявімо собі, що ми маємо клас Людина
та інший клас Student
,
який є нащадком класу Людина
:
З виводів видно, що об'єкт $студент
одночасно вважається
екземпляром обох класів – Student
і Людина
.
Fluent Interfaces
„Плавний інтерфейс“ (англійською „Fluent Interface“) – це техніка в ООП, яка дозволяє ланцюжком викликати методи один за одним в одному виклику. Це часто спрощує та робить код зрозумілішим.
Ключовим елементом плавного інтерфейсу є те, що кожен метод у
ланцюжку повертає посилання на поточний об'єкт. Цього ми досягаємо тим,
що в кінці методу використовуємо return $this;
. Цей стиль
програмування часто асоціюється з методами, що називаються
„сеттерами“, які встановлюють значення властивостей об'єкта.
Покажемо, як може виглядати плавний інтерфейс на прикладі надсилання електронних листів:
У цьому прикладі методи setFrom()
, setRecipient()
та setMessage()
служать для встановлення відповідних значень (відправника,
одержувача, вмісту повідомлення). Після встановлення кожного з цих
значень методи повертають поточний об'єкт ($email
), що дозволяє нам
викликати наступний метод у ланцюжку. Нарешті, ми викликаємо метод
send()
, який фактично надсилає електронний лист.
Завдяки плавним інтерфейсам ми можемо писати код, який є інтуїтивно зрозумілим та легко читабельним.
Копіювання за допомогою clone
У PHP ми можемо створити копію об'єкта за допомогою оператора
clone
. Таким чином ми отримаємо новий екземпляр з ідентичним
вмістом.
Якщо нам потрібно під час копіювання об'єкта змінити деякі його
властивості, ми можемо визначити в класі спеціальний метод
__clone()
. Цей метод автоматично викликається, коли об'єкт
клонується.
У цьому прикладі ми маємо клас Вівця
з однією властивістю
$імя
. Коли ми клонуємо екземпляр цього класу, метод __clone()
дбає про те, щоб ім'я клонованої вівці отримало префікс „Клон“.
Трейди
Трейди в PHP – це інструмент, який дозволяє спільно використовувати методи, властивості та константи між класами та запобігти дублюванню коду. Ви можете уявити їх як механізм „копіювати та вставити“ (Ctrl-C та Ctrl-V), коли вміст трейту „вставляється“ в класи. Це дозволяє вам повторно використовувати код без необхідності створювати складні ієрархії класів.
Давайте покажемо простий приклад, як використовувати трейти в PHP:
У цьому прикладі ми маємо трейт під назвою Сигналення
, який
містить один метод посигналити()
. Потім ми маємо два класи:
Автомобіль
та Вантажівка
, які обидва використовують
трейт Сигналення
. Завдяки цьому обидва класи „мають“ метод
посигналити()
, і ми можемо викликати його на об'єктах обох
класів.
Трейди дозволяють вам легко та ефективно спільно використовувати
код між класами. При цьому вони не входять до ієрархії успадкування,
тобто $автомобіль instanceof Сигналення
поверне false
.
Винятки
Винятки в ООП дозволяють нам елегантно обробляти помилки та неочікувані ситуації в нашому коді. Це об'єкти, які несуть інформацію про помилку або незвичайну ситуацію.
У PHP є вбудований клас Exception
, який служить основою для всіх
винятків. Він має кілька методів, які дозволяють нам отримати більше
інформації про виняток, як-от повідомлення про помилку, файл і рядок, де
сталася помилка, тощо.
Коли в коді виникає помилка, ми можемо „викинути“ виняток за
допомогою ключового слова throw
.
Коли функція ділення()
отримує нуль як другий аргумент, вона
викине виняток з повідомленням про помилку 'Ділення на нуль!'
.
Щоб запобігти аварійному завершенню програми під час викидання
винятку, ми перехоплюємо його в блоці try/catch
:
Код, який може викинути виняток, загортається в блок try
. Якщо
виняток викинуто, виконання коду переходить до блоку catch
, де ми
можемо обробити виняток (наприклад, вивести повідомлення про
помилку).
Після блоків try
та catch
ми можемо додати необов'язковий
блок finally
, який виконується завжди, незалежно від того, чи було
викинуто виняток (навіть якщо в блоці try
або catch
ми
використовуємо оператор return
, break
або continue
):
Ми також можемо створити власні класи (ієрархію) винятків, які успадковують від класу Exception. Як приклад, уявімо собі простий банківський додаток, який дозволяє здійснювати поповнення та зняття коштів:
Для одного блоку try
можна вказати кілька блоків catch
,
якщо ви очікуєте різні типи винятків.
У цьому прикладі важливо звернути увагу на порядок блоків catch
.
Оскільки всі винятки успадковують від БанківськийВиняток
, якби
цей блок був першим, у ньому були б перехоплені всі винятки, і код не
дійшов би до наступних блоків catch
. Тому важливо розміщувати
більш специфічні винятки (тобто ті, що успадковують від інших) у блоці
catch
вище за порядком, ніж їхні батьківські винятки.
Ітерація
У PHP ви можете перебирати об'єкти за допомогою циклу foreach
,
подібно до того, як ви перебираєте масиви. Щоб це працювало, об'єкт
повинен реалізувати спеціальний інтерфейс.
Перший варіант – реалізувати інтерфейс Iterator
, який має методи
current()
, що повертає поточне значення, key()
, що повертає ключ,
next()
, що переходить до наступного значення, rewind()
, що
переходить на початок, та valid()
, що перевіряє, чи ми ще не дійшли
до кінця.
Другий варіант – реалізувати інтерфейс IteratorAggregate
, який має
лише один метод getIterator()
. Він або повертає об'єкт-заступник, який
забезпечуватиме ітерацію, або може представляти генератор, що є
спеціальною функцією, в якій використовується yield
для
послідовного повернення ключів та значень:
Найкращі практики
Коли ви засвоїли основні принципи об'єктно-орієнтованого програмування, важливо зосередитися на найкращих практиках в ООП. Вони допоможуть вам писати код, який є не тільки функціональним, але й читабельним, зрозумілим та легким у підтримці.
- Розділення відповідальності (Separation of Concerns): Кожен клас повинен мати чітко визначену відповідальність і вирішувати лише одне основне завдання. Якщо клас робить занадто багато речей, може бути доцільно розділити його на менші, спеціалізовані класи.
- Інкапсуляція (Encapsulation): Дані та методи повинні бути якомога більше прихованими та доступними лише через визначений інтерфейс. Це дозволить вам змінювати внутрішню реалізацію класу, не впливаючи на решту коду.
- Впровадження залежностей (Dependency Injection): Замість того, щоб створювати залежності безпосередньо в класі, ви повинні „впроваджувати“ їх ззовні. Для глибшого розуміння цього принципу рекомендуємо розділи про Dependency Injection.