Зачем нужно ООП

  1. Базовые понятия ООП
  2. Код без ООП
  3. Философия ООП
  4. Когда нужно использовать ООП
  5. Когда НЕ нужно использовать ООП

Базовые понятия ООП

Класс - описание логики поведения (через свойства и методы).

Объект - экземпляр класса, который содержит данные и может выполнять логику класса.

Интерфейс - функции, которые должен реализовать класс.

Ещё есть наследование (когда один класс расширяет другой), полиморфизм (когда класс-потомок меняет логику поведения родителя), инкапсуляция (красивое слово, которое означает "сокрытие логики" или "давай запихаем эту хрень так, чтобы её никто никогда не нашёл"). Не расстраивайтесь, если вы не знаете точного определения какого-то из модных программерских слов, в 99% случаев можете сказать, что это "хрень какая-то", не ошибётесь.

Важно помнить: в ООП данные хранятся в объектах, а логика в классах. Объекты всегда создаются из класса.

Код без ООП

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

/**
 * Скрипт, который выводит пользователей в виде списка
 */

// Данные пользователей
$users = [
    ['name' => 'Вася'],
    ['name' => 'Петя'],
    ['name' => 'Коля'],
    /* ещё миллион записей */
];

// Показываем список
foreach($users as $user) {
    echo $user['name'], PHP_EOL;
}

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

// Текущая страница и лимит записей на странице
$page = 1;
$limit = 2;

// Показываем пользователей на текущей странице
foreach (array_slice($users, ($page - 1) * $limit, $limit) as $user) {
    echo $user['name'], PHP_EOL;
}

Давайте добавим в вывод ещё тэги HTML, чтобы список был более красивым:

// Показываем пользователей текущей страницы в виде HTML-списка
echo '<ol start="', $page * $limit ,'">';
foreach (array_slice($users, ($page - 1) * $limit, $limit) as $user) {
    echo '<li>', htmlentities($user['name']), '</li>', PHP_EOL;
}
echo '</ol>';

Для полного счастья здесь не хватает разделения чётных и нечётных строк. Давайте добавим:

// Показываем пользователей текущей страницы в виде HTML-списка с разделением на "чётные" и "нечётные" строки
echo '<ol start="', $page * $limit ,'">';
foreach (array_slice($users, ($page - 1) * $limit, $limit) as $i => $user) {
    $class = ($i + 1) % 2 ? 'odd' : 'even';
    echo '<li class="', $class, '">', htmlentities($user['name']), '</li>', PHP_EOL;
}
echo '</ol>';

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

А что будет, если нам скажут, что имя пользователя должно быть ссылкой? А что будет, если вместо списка потребуется таблица со множеством колонок? А что будет, если нам надо будет выгрузить список в PDF? А что будет, если ... (в этом месте можно подставить любые обстоятельства).

Если код оставить в том виде, в котором он сейчас есть, то рано или поздно он перестанет быть управляемым и начнёт диктовать разработчикам свои условия. То есть вместо того, чтобы добавлять новые плюшки, программист будет больше думать, как бы не сломать старое.

Философия ООП

Первое, что нужно понять, ООП это лишь один из способов организации кода. С помощью объектов мы разделяем большую логику на на много маленьких, чтобы нам легче было управлять своим же кодом. Это значит, что не нужно во все места совать классы и интерфейсы. Если ООП не упрощает код, а только запутывает, то это неправильное ООП! Каждый раз спрашивайте себя: "А что я упрощу, если сделаю объект?"

Но давайте вернёмся к примеру выше и посмотрим, как можно упростить себе жизнь.

Первое, на что нужно обратить внимание, это то, что мы работаем с пользователем как с массивом:

$user['name']

А что будет, если свойств у пользователя будет много или ключи массива будут меняться? Тогда придётся где-то вести документацию, какие ключи можно использовать, а какие нет. И кто-то должен будет контролировать, чтобы программисты использовали только правильные ключи. Оказывается, эту задачу можно переложить на среду исполнения, если использовать ООП.

Давайте создадим класс пользователя, который на вход принимает данные пользователя и имеет функцию для возврата имени:

/**
 * Пользовтель
 */
class User
{
    private $data = [];
    
    function __construct(array $data = []) {
        $this->data = $data;
    }
    
    function name() {
        return $this->data['name'] ?? null;
    }
}

Исправим объявление списка:

// Данные пользователей
$users = [
    new User(['name' => 'Вася']),
    new User(['name' => 'Петя']),
    new User(['name' => 'Коля']),
    /* ещё миллион записей */
];

Теперь для доступа к имени пользователя можно использовать функцию $user->name(). Мы "спрятали" (инкапсулировали) структуру исходного массива с данными пользователя за функциями класса User. Если исходный массив поменяется, то изменения нужно будет внести только в функции класса.

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

class Collection {
    private $array = null;
    
    function __construct(array $array = []) {
        $this->array = $array;
    }
    
    // Возвращает данные на одной странице
    function itemsOnPage($page, $limit) {
        return array_slice($this->array, ($page - 1) * $limit, $limit);
    }
}

Изменим объявление исходных данных:

// Данные пользователей
$users = new Collection([
    new User(['name' => 'Вася']),
    new User(['name' => 'Петя']),
    new User(['name' => 'Коля']),
    /* ещё миллион записей */
]);

Теперь можно обращаться к нашим данным так:

// Постраничный вывод пользователей
foreach($users->itemsOnPage($page, $limit) as $user) {
    echo $user->name(), PHP_EOL;
}

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

// Получаем список пользователей
$users = $users->itemsOnPage($page, $limit);

// Используем шаблонизатор Twig для отображения списка
echo $twig->render('users.html', [
    'start' => ($page - 1) * $limit,
    'users' => $users,
]);

Шаблон представляет из себя псевдо язык разметки, в котором в теории удобно делать вёрстку:

{# Шаблон для отображения списка пользователей #}
<ol start="{{start}}">
    {% for user in users %}
        <li class="{{loop.index is even ? 'even' : 'odd'}}">{{user.name() | escape}}</li>
    {% endfor %}
</ol>

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

Когда нужно использовать ООП

Вот ситуации, когда, на мой взгляд, ООП даёт преимущества:

  1. Работа с базой данных Через ООП можно "скрыть" внутреннюю структуру базы от конечного пользователя (в данном случае программиста). Если база будет меняться, то это не приведёт к переписыванию всего проекта. Если вы используете объект, вам неважно, как он хранится в базе данных.
  2. Сложная логика С помощью ООП можно разбить сложную логику на несколько простых классов. Преимущество в том, что можно создавать универсальный интерфейс и узкоспециализированные классы. Вам не придётся менять код, если потребуется добавить "что-то похожее вон на тот класс".
  3. Большой проект Ахиллесовой пятой больших проектов является сильное связывание, когда один код вызывается из множества разных частей системы. В последствии такой код становится "неприкасаемым". То есть разработчики боятся в нём что-то менять, потому что неизвестно, какие части проекта после этого отвалятся. Если использовать ООП, то с такой проблемой будет разы проще справиться. Объект можно разбить, сделать фасадом или написать тест. С обычным кодом такое сделать труднее.
  4. Автоматическое тестирование Поскольку объекты несут в себе конечную логику (имеется в виду, что объекты имеют конечное число свойств и методов), то их можно тестировать. Вызвал один метод, проверил результат, вызвал другой метод, проверил результат и так далее. Автоматические тесты помогают избежать ошибок и за счёт этого улучшают скорость и качество разработки.

Когда НЕ нужно использовать ООП

Я часто слышу мнение, что ООП нужно использовать везде и всегда. Нет! ООП должно помогать программисту писать код, а не превращать его жизнь в ад из "высоких стандартов опп".

Ситуации, когда ООП не даст вам преимуществ:

  1. Однофайловые скрипты Бывают ситуации, когда нужно сделать что-то быстро и одноразово. Например, выгрузку или отчёт. НЕ нужно тратить время на продумывание объектов и разнесение логики. Сделайте всё в одном файле. Это будет быстрее и проще для изменения. Удалить один файл легче, чем выковыривать потом классы по всему проекту.
  2. Отчёты Три раза подумайте перед тем, как пытаться сделать "универсальный построитель отчётов". Логику в больших отчётах далеко не всегда можно описать идиомами ООП. Лучше использовать однофайловые скрипты с простой линейной логикой, чем тратить время на придумывание монстров из классов.
  3. SQL-билдеры Чем проще SQL, тем крепче спит программист. Если вы используете ООП для генерирования SQL, то и понятия не имеете, насколько сложным будет конечный запрос. А что ещё хуже, билдеры имеют свои баги, которые приводят к скрытым ошибкам проекта. Вообще, SQL в проектах нужен только в двух местах: отчётах и репозиториях. Но в отчётах слишком сложный SQL, чтобы использовать билдер, а в репозиториях слишком простой.
  4. MVC в популярных PHP-фрейморках Я уже писал о проблемах MVC в PHP. Если кратко, то использование MVC приносит больше вреда, чем пользы, из-за неправильного понимания самого шаблона. Классы-контроллеры пухнут и превращаются в монстров, которыми сложно управлять. Единственный плюс MVC, это наличие шаблонов. Но шаблоны можно подключить и без MVC,

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

Хорошая статья, мне понравилась. Оставлю отзыв!