Прекратите использовать “extends” в PHP

Архитектура многих фреймворков зачастую требует расширения своих классов. Но сегодня речь только про чистый от фреймворков код, за который вы и/или ваша команда несет ответственность.

В первую очередь хочется сказать всем: “Ставьте ключевое слово final классам всегда, когда это возможно. В первую очередь тем, кто реализует какой либо интерфейс”. Читатель может возразить: “Ведь это снижает гибкость”. На что отвечу, что гибкость, произрастает из хороших абстракций, а не из наследований.

8 причин использования ключевого слова final

Существует множество причин выставлять ключевое слово final для ваших классов:

1. Предотвратить Extend Hell

У разработчиков есть плохая привычка решать проблемы через наследования, подставляя для работы новый исправленный унаследованный класс

<?php

class Db { /* ... */ }
class Core extends Db { /* ... */ }
class User extends Core { /* ... */ }
class Admin extends User { /* ... */ }
class Bot extends Admin { /* ... */ }
class BotThatDoesSpecialThings extends Bot { /* ... */ }
class PatchedBot extends BotThatDoesSpecialThings { /* ... */ }

Это, без каких-либо сомнений, то, как вы НЕ должны разрабатывать свой код.

Описанный выше подход обычно используется разработчиками, которые путают ООП с “способом решения проблем с помощью наследования” (может быть, “программирование, ориентированное на наследование”?).

<?php

interface UserInterface {}

final class RegularWebUser implements UserInterface {}
final class AdminUser implements UserInterface {}
final class PowerAdminUser implements UserInterface {}
final class WipFunctionalityUser implements UserInterface {}

Многие возразят мне: “Почему, посмотри, сколько кода будет дублироваться, можем ли мы хотя бы использовать трейты?” Но тогда этот трейт станет узким горлышком.

Перестаньте бояться дублированного кода

Так работает наш мозг: он заставляет вас искать шаблоны. Избегание абстрактных классов и наследования поможет вам избежать неудачных решений в коде.

Вам нужны какие-либо новые функции? Начните с интерфейса. Используйте doc-блоки для описания ввода, вывода и причины, стоящей за этим. Это может показаться замедлением, но это поможет вам спланировать то, что вам действительно нужно.

2. Поощрение композиции

В целом, принудительное предотвращение наследования (по умолчанию) имеет приятное преимущество, заставляя разработчиков больше думать о композиции.

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

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

https://en.wikipedia.org/wiki/Feature_creep

Возьмем следующий пример:

<?php

class RegistrationService implements RegistrationServiceInterface
{
    public function registerUser(/* ... */) { /* ... */ }
}

class EmailingRegistrationService extends RegistrationService
{
    public function registerUser(/* ... */) 
    {
        $user = parent::registerUser(/* ... */);

        $this->sendTheRegistrationMail($user);

        return $user;
    }

    // ...
}

Добавим классу ключевое слово final, что заставит нас использовать композицию:

<?php

final class EmailingRegistrationService implements RegistrationServiceInterface
{
    public function __construct(RegistrationServiceInterface $mainRegistrationService) 
    {
        $this->mainRegistrationService = $mainRegistrationService;
    }

    public function registerUser(/* ... */) 
    {
        $user = $this->mainRegistrationService->registerUser(/* ... */);

        $this->sendTheRegistrationMail($user);

        return $user;
    }

    // ...
}

3. Разработчик начинает задумываться над общедоступном API

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

<?php

class RegistrationService implements RegistrationServiceInterface
{
    protected $db;

    public function __construct(DbConnectionInterface $db) 
    {
        $this->db = $db;
    }

    public function registerUser(/* ... */) 
    {
        // ...

        $this->db->insert($userData);

        // ...
    }
}

class SwitchableDbRegistrationService extends RegistrationService
{
    public function setDb(DbConnectionInterface $db)
    {
        $this->db = $db;
    }
}

Этот пример показывает ряд недостатков в мыслительном процессе, которые привели к SwitchableDbRegistrationService:

  • Этот setDb метод используется для изменения DbConnectionInterface во время выполнения, что, по-видимому, скрывает другую решаемую проблему: может быть, нам нужно MasterSlaveConnection?
  • Этот метод setDb не подпадает под действие RegistrationServiceInterface, поэтому мы можем использовать его только тогда, когда мы строго связываем наш код с SwitchableDbRegistrationService, что в некоторых контекстах противоречит цели самого контракта.
  • setDb изменяет зависимости во время выполнения, и это может не поддерживаться логикой RegistrationService, а также может привести к ошибкам.
  • Возможно, метод setDb был введен из-за ошибки в первоначальной реализации: почему исправление было предоставлено таким образом? Действительно ли это решение проблемы или оно устраняет только симптом?

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

4. Разработчик теперь будет обязан думать над упрощением общедоступного API

Поскольку классы с большим количеством общедоступных методов с большой вероятностью нарушат SRP, часто бывает, что разработчик захочет переопределить определенный API этих классов.

Начало использования final заставляет разработчика заранее думать о новых API и о том, чтобы сделать их как можно меньше.

5. final всегда можно сделать расширяемым

Вы можете сделать любой final класс расширяемым в любой момент времени (если это действительно требуется).

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

6. extends нарушает инкапсуляцию

Расширение класса нарушает инкапсуляцию и может привести к непредвиденным последствиям: дважды подумайте, прежде чем использовать ключевое слово extends, или, что еще лучше, создайте свои классы final и избавьте других от необходимости думать об этом.

7. Вам не нужна такая гибкость

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

Мой контраргумент очень прост: вам не нужна такая гибкость.

Зачем вам это нужно в первую очередь? Почему вы не можете написать свою собственную индивидуальную реализацию контракта? Почему вы не можете использовать композицию? Вы тщательно обдумали проблему?

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

8. Вы можете свободно изменять код

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

Поскольку инкапсуляция гарантированно сохраняется, единственное, о чем вам нужно заботиться, – это общедоступный API.

Теперь вы можете переписывать все, столько раз, сколько захотите.

Когда следует избегать final:

Final классы эффективно работают только при следующих предположениях:

  1. Существует абстракция (интерфейс), которую реализует конечный класс
  2. Все общедоступные API final класса являются частью этого интерфейса

Если одно из этих двух предварительных условий отсутствует, то вы, вероятно, достигнете момента времени, когда сделаете класс расширяемым, поскольку ваш код на самом деле не полагается на абстракции.

Исключение может быть сделано, если конкретный класс представляет набор ограничений или концепций, которые являются полностью неизменяемыми, негибкими и глобальными для всей системы. Хорошим примером является математическая операция: $calculator->sum($a, $b)вряд ли изменится со временем. В этих случаях можно с уверенностью предположить, что мы можем использовать ключевое слово final без абстракции, на которую нужно полагаться в первую очередь.

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

Рейтинг
( Пока оценок нет )
Maxyc Webber/ автор статьи
Мне 35 лет. Опыт профессиональной разработки 15 лет. Занимаюсь разработкой и поддержкой корпоративных систем автоматизации бизнеса, а также высоконагруженными проектами. Мне нравится решать нестандартные проблемы бизнеса. Имею опыт формирования команд под проект, налаживания процесса разработки, коммуникации программистов и заказчиков. Есть опыт работы с зарубежными заказчиками (ОАЭ, Польша, Германия, Швейцария).
Понравилась статья? Поделиться с друзьями:
Комментарии: 4
  1. Александра

    Все расписано достаточно подробно. Служба техподдержки Пин ап если нужно также подскажет как пин ап скачать и актуальный адрес рабочего зеркала.

  2. Виктор

    Интересная информация. Могу посоветовать ремонт стиральных машин индезит делать только в Ремонт от профи, не прогадаете.

  3. Константин

    Спасибо вам за предоставленную информацию. Известно, что комфортная атмосфера жилья современного человека вряд ли возможна без интерьера, созданного профессионалами. Каждый по своему видит, как должен выглядеть дом его мечты. Дизайн интерьера – это гармоничное сочетание функциональности, эстетичности и удобства. Студия дизайна https://www.cpv.ru/modules/publisher/item.php?itemid=5962 всегда учитывает пожелания заказчика и создает такой дизайн-проект, воплощающий его мечту в жизнь. Опытные специалисты студии используют современные методики, включая 3D-визуализацию и технический дизайн-проект готовят в полном соответствии с вкусами и потребностями клиента.

  4. Мила

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

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!:

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.