Статьи

Практический рефакторинг PHP: метод Hide

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

Зачем возиться с видимостью?

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

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

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

Методы используют все различные степени охвата, но делая их защищенными или частными, они освещают открытый протокол класса (остальные методы).

Реальные ограничения объема

Частные средства доступны только из одного класса; но в некоторых языках, таких как PHP и Python, недоступность означает просто доступ с некоторыми трудностями. Если вы сконфигурируете члена класса как частного, он будет доступен из того же или другого объекта, где он определен, но только для одного и того же класса; вам нужно будет только посмотреть на один исходный файл для выполнения изменений.

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

Начиная с PHP 5.3, отражение позволяет установить область действия поля. Doctrine 2 использует это для воссоздания объектов из результата запроса к базе данных, вероятно, единственной разумной причиной для этой функции, кроме отладки.

Начиная с PHP 5.4 (в настоящее время нестабильно), привязка замыканий позволяет получить доступ к любому закрытому или защищенному полю, просто имея ссылку на объект.

Теперь внимательно прочитайте инструкцию по эксплуатации:


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

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

меры

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

Вместо этого я могу предложить вам свои чеки:

  • Выполните grep -r ‘methodName (‘., Чтобы получить представление о том, где используется метод. Это не является максимально надежным из-за динамических вызовов через call_user_func (), $ object -> $ method () и т. Д. Если метод используется за пределами целевой области, вам придется разобраться с этими случаями, прежде чем изменять его видимость.
  • Проверьте охват ваших методов в наборе тестов . Вы можете изменить только рассматриваемый код, особенно в случае, когда простой процесс компиляции в код операции op в PHP не поможет обнаружить частные методы, вызываемые вне их области действия. Добавьте тесты, чтобы покрыть методы, но только косвенно (если вы вызываете их из пакета, их закрытие будет нарушено).
  • Если есть вызов из непокрытого кода , вы не должны продолжать рефакторинг. Если у метода есть тесты, разве это не признак того, что его нужно извлекать как открытый метод для частного участника?

Если все в порядке, рефакторинг прост.

  1. Ограничьте выбранные методы как частные или защищенные.
  2. Запустите набор тестов, чтобы убедиться, что они вызваны не из того места.

пример

В исходном состоянии объект Presenter Book создает некоторый HTML-код (вы можете называть его помощником вида, если предпочитаете это имя.) Открытый метод вызывается прямо внутри класса: нет причин для его дальнейшего раскрытия.

<?php
class HideMethod extends PHPUnit_Framework_TestCase
{
    public function testTheBookIsRenderedCorrectly()
    {
        $book = new BookInfo('Robots and Empire', 'Asimov');
        $this->assertEquals('<li>Robots and Empire <em>(Asimov)</em></li>', $book->__toString());
    }
}

class BookInfo
{
    private $title;
    private $author;

    public function __construct($title, $author)
    {
        $this->title = $title;
        $this->author = $author;
    }

    public function __toString()
    {
        $authorInfo = $this->authorHtml();
        return "<li>$this->title $authorInfo</li>";
    }

    public function authorHtml()
    {
        return "<em>($this->author)</em>";
    }
}

grep говорит нам, что не нужно беспокоиться о других вызовах (очевидно, это пример):

[16:00:40][giorgio@Desmond:~/Dropbox/practical-php-refactoring]$ grep -r 'authorHtml(' *.php
HideMethod.php:        $authorInfo = $this->authorHtml();
HideMethod.php:    public function authorHtml()

Метод покрыт? Да, мы видим тест, вызывающий __toString (), который косвенно вызывает его … Возможно, вы захотите использовать автоматический отчет PHPUnit в реальном коде.

Измените видимость сейчас:

<?php
class HideMethod extends PHPUnit_Framework_TestCase
{
    public function testTheBookIsRenderedCorrectly()
    {
        $book = new BookInfo('Robots and Empire', 'Asimov');
        $this->assertEquals('<li>Robots and Empire <em>(Asimov)</em></li>', $book->__toString());
    }
}

class BookInfo
{
    private $title;
    private $author;

    public function __construct($title, $author)
    {
        $this->title = $title;
        $this->author = $author;
    }

    public function __toString()
    {
        $authorInfo = $this->authorHtml();
        return "<li>$this->title $authorInfo</li>";
    }

    private function authorHtml()
    {
        return "<em>($this->author)</em>";
    }
}

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

[16:03:50][giorgio@Desmond:~/Dropbox/practical-php-refactoring]$ phpunit HideMethod.php
PHPUnit 3.6.4 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 1 assertion)