Статьи

Практический рефакторинг PHP: инкапсуляция поля

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

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

Почему я должен скрывать публичные объекты?

Базовая альтернатива общедоступному полю, геттерам и сеттерам, позволяет вводить косвенное обращение в случае необходимости. Например, вы можете нормализовать значение поля или преобразовать его в вычисляемое поле, поскольку контракт охватывает только метод ($ object-> getName ()), а не фактическое поле ($ this-> name).

Вы также можете опустить метод получения или установки, чтобы получить соответственно закрытое поле, которое полностью инкапсулировано , или конечное поле, которое нельзя изменить после завершения построения объекта. В PHP нет окончательного ключевого слова, хотя оно копирует большую часть объектной модели Java, которая была наиболее распространенной, когда в PHP 5 была представлена ​​новая парадигма.

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

меры

  1. Создать геттеры и сеттеры . Просто добытчик, если поле уже оценено в процессе строительства. В противном случае, получатель и установщик позволяют вносить изменения.
  2. Замените присваивания и показания поля вызовами к получателю или установщику. В случае, если есть назначения, но вы не намереваетесь предоставлять метод установки, лучше вставить метод @deprecated setField () и выполнить его рефакторинг позже, как только все обращения к полю будут проходить оттуда.
  3. Убедитесь, что тесты все еще зеленые — они должны работать как есть после изменений, за исключением тех же изменений в использовании и назначении. В ходе этого рефакторинга не было внесено больших изменений в дизайн.
  4. Объявите поле как частное или защищенное. Если у вас уже нет подклассов или вы намерены разрешить использование подклассов в качестве документированной точки расширения, я бы выбрал частную видимость.

В мире PHP существовала распространенная тенденция всегда предоставлять защищенные поля, но не все классы должны (или использоваться) использоваться для создания подклассов, а для некоторых объектов даже нет выбора замены оригиналов в графе объектов каркаса. Беглый взгляд на Symfony 2 показывает, что приватность больше не является табу, в то время как большая часть Zend Framework 2 состоит из портированного кода 1.x и по-прежнему содержит много защищенных полей (это может быть дизайнерским решением).

пример

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

<?php
class EncapsulateField extends PHPUnit_Framework_TestCase
{
    public function testTheFieldCanBeManipulated()
    {
        $reservation = new Reservation();
        $reservation->date = '2010-01-01';
        $this->assertTrue($reservation->isOutdated());
    }
}

class Reservation
{
    public $date;

    public function isOutdated()
    {
        // global state! Avoid this in real code
        return $this->date < date('Y-m-d');
    }
}

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

class Reservation
{
    public $date;

    /**
     * @param string $date
     */
    public function setDate($date)
    {
        $this->date = $date;
    }

    public function isOutdated()
    {
        // global state! Avoid this in real code
        return $this->date < date('Y-m-d');
    }
}

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

<?php
class EncapsulateField extends PHPUnit_Framework_TestCase
{
    public function testTheFieldCanBeManipulated()
    {
        $reservation = new Reservation();
        $reservation->setDate('2010-01-01');
        $this->assertTrue($reservation->isOutdated());
    }
}

Теперь мы можем безопасно ограничить видимость $ this-> date частным и проверить, что нет прямых ссылок на поле снаружи, что приведет к сбою тестов, выполняющих их. PHP не проверяет доступ к элементам на этапе компиляции «на лету»: вам нужны тесты, выполняющие весь ваш код, чтобы убедиться, что вы не обращаетесь к переменной, которая стала закрытой.

class Reservation
{
    private $date;

    /**
     * @param string $date
     */
    public function setDate($date)
    {
        $this->date = $date;
    }

    public function isOutdated()
    {
        // global state! Avoid this in real code
        return $this->date < date('Y-m-d');
    }
}