Проблеми при проектуванні ООП на PHP

26

Від автора: об’єктно-орієнтований підхід як в PHP, так і в будь-якому іншому мовою програмування досить не простий, так як вимагає особливого мислення і тому новачкам важко перебудуватися після процедурного підходу, який, як правило, вивчається першим. Тому в даній статті ми з Вами розглянемо типові помилки при проектуванні ООП на PHP, які як показує практика, неминучі на ранніх етапах вивчення.

Програмування з використанням об’єктно-орієнтованого програмування, це не просто рішення поставленої задачі, це реалізація гнучкого і модульного проекту, який, звичайно ж, буде задовольняти поставленим вимогам, буде простий підтримки і розширення. При цьому, як правило, добре спроектовані компоненти – класи – подібного проекту, без істотних правок, можуть використовуватися в інших додатках. Взагалі, модульність проекту, це найважливіша його особливість, так як дозволяє, просто вносити зміни та доповнення.

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

Тому давайте розглянемо типові проблеми при проектуванні ООП в PHP.

Помилка №1. Відсутність спадкування.

Проектуючи структуру майбутнього проекту, як правило, новачки створюють величезну кількість самостійних класів, для кожного елемента скрипта, і при цьому, дублюючи деякі загальні ділянки коду. Звичайно, клас максимально повинен бути незалежним від інших класів, тим самим забезпечується можливість його повторного використання при вирішенні інших завдань. Але це теж не вихід — плодити в додатку масу повторюваного коду. Зверніть увагу на такий приклад.

class IndexPage {
protected $title;
protected $db;
public function __construct() {
$this->db = new mysqli(HOST,USER,PASS,DB);
}
public function getContent () {
$query = «Select * FROM articles»;
$data = $this->db->query($query);
return $data;
}
public function render($data) {
echo «

«;
echo $data;
echo «

«;
}
}
class ArticlePage {
protected $title;
protected $db;
public function __construct() {
$this->db = new mysqli(HOST,USER,PASS,DB);
}
public function getContent ($id) {
$query = «Select * FROM articles WHERE id = «.$id;
$data = $this->db->query($query);
return $data;
}
public function render($data) {
echo «

«;
echo $data;
echo «

«;
}
}

Ми бачимо два самостійних класу, які виводять на екран вміст певних сторінок веб-додатки. Кожен з них успішно виконує поставлені завдання. Але при цьому, вони практично ідентичні, за винятком методу getContent(). Тому, не забувайте про таке поняття як спадкування, завдяки якому, Ви зможете реалізувати ієрархію класів в розробляється скрипті, а значить усі загальні елементи (властивості і методи), будуть винесені на класи, які стоять рівнем вище.

class Page {
protected $title;
protected $db;
public function __construct() {
$this->db = new mysqli(HOST,USER,PASS,DB);
}
public function render($data) {
echo «

«;
echo $data;
echo «

«;
}
}
class IndexPage extends Page {
public function getContent () {
$query = «Select * FROM articles»;
$data = $this->db->query($query);
return $data;
}
}
class ArticlePage extends Page {
public function getContent ($id) {
$query = «Select * FROM articles WHERE id = «.$id;
$data = $this->db->query($query);
return $data;
}
}

Як раз це і показано на прикладі вище. Так, кількість класів збільшилася, але в результаті цього, код підкласів значно скорочений і батьківський клас Page – являє собою основне логічне ядро, в якому зосереджена вся загальна логіка проекту. Тому ще раз повторююсь – використовуйте спадкування для формування ієрархій класів у структурі майбутнього додатки.

Помилка №2. Відсутність інтерфейсів.

Дуже часто для виконання якихось певних дій у веб-додатку передбачені різні способи виконання поставленого завдання. Наприклад, для збереження певних повідомлень користувачів, може використовуватися база даних, файлова структура або механізм сесій. Тобто іншими словами одне і те ж дію – збереження даних, Ви можете виконати різними способами, в залежності від певних умов або побажання користувача. Відповідно для опису функціоналу кожного із способів, звичайно ж, використовуються окремі класи, структура яких може бути приблизно наступної:

class DbSave {
public $db;
public function __construct() {
$this->db = new mysqli($host,$user,$pass,$db);
}
public function saveInDataBase($data) {
if($data) {
$query = «INSERT INTO comments(name,message)
VALUES(‘».$data[‘name’].»‘,'».$data[‘message’].»‘)»;
$result = $this->db->query($query);
}
}
}
class FileSave{
public function saveInFile($data) {
file_put_contents(‘messages.txt’,$data);
}
}

Зверніть увагу, що кожен клас пропонує власний метод по виконанню вище поставленого завдання і, здавалося б, все повинно успішно працювати, але для зміни способу збереження повідомлення доведеться створити об’єкт необхідного класу і викликати на виконання відповідний метод. Тобто іншими словами багато чого доведеться переписати. Так як для використання першого класу, необхідно прописати наступне:

$obj = new DbSave(‘localhost’,’user’,’password’,’database’);
$obj->saveInDataBase(array(‘name’,’message’));

А для другого:

$obj = new FileSave();
$obj->saveInFile(array(‘title’,’text’));

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

interface ISave {
public function save($data);
}
class DbSave implements ISave {
public $db;
public function __construct() {
$this->db = new mysqli($host,$user,$pass,$db);
}
public function save($data) {
if($data) {
$query = «INSERT INTO post(name,messages)
VALUES(‘».$data[‘name’].»‘,'».$data[‘messages’].»‘)»;
$result = $this->db->query($query);
}
}
}
class FileSave implements ISave {
public function save($data) {
file_put_contents(‘file.txt’,$data);
}
}

Таким чином, для вибору потрібної способу збереження повідомлення користувача, досить вибрати клас і створити його об’єкт і викликати на виконання метод save(). Тобто об’єкти, Ви можете міняти, але доступ до необхідного функціоналу так або інакше здійснюється через один єдиний метод save(), який визначається інтерфейсом. Що дуже зручно і зводить необхідні правки до мінімуму.

$obj = new FileSave();
$obj->save(array(‘title’,’text’));

Помилка №3. Повсюдне використання специфікатора доступу public.

Звичайно, зручно при оголошенні методів і властивостей, використовувати вищезгадану область видимості і звертатися до них безпосередньо з об’єкта, до того ж код виходить коротким і зручно читаним.

login = ‘ben’;
echo $user->login;

Даний приклад хоч і вирішує поставлену задачу, але з точки зору парадигми ООП — дуже погано спроектований. Бо, якщо щось змінити в об’єкті, то обов’язково доведеться вносити правки в код, який його використовує.
А тепер давайте розглянемо наступний код:

login = $login;
}
public function getLogin($login) {
return $this->login;
}
}
$user = new User();
$user->setLogin(‘ben’);
echo $user->getLogin(‘ben’);

Зверніть увагу, використовуючи специфікатори доступу, Ви забороняєте прямий доступ до певних елементів класу — в нашому випадку до властивості $login. Таким чином, для того щоб визначити чи отримати його значення, слід використовувати відкриті методи setLogin() і getLogin(), а значить, якщо щось змінити в реалізації класу, код використовує його функціонал міняти не доведеться. Наприклад, якщо потрібно додати якусь логіку при формуванні логіну користувача, необхідно лише змінити метод setLogin().

Таким чином, ми з Вами розглянули типові проблеми при проектуванні ООП PHP, звичайно це тільки найбільш часто зустрічаються серед новачків і насправді їх дуже і дуже багато. Але не лякайтеся, помилки – це нормально як при навчанні, так і при професійній роботі, просто з ростом Вашого досвіду їх кількість поступово буде зменшуватися. Більш детально тема спадкування і формування інтересів розглянута в преміум-курсі курсі Об’єктно-орієнтоване програмування на PHP.

На цьому дана стаття завершена. Всього Вам доброго і вдалого кодування!!!