Статьи

Практический рефакторинг PHP: замена массива на объект

Этот рефакторинг является специализацией Replace Data Value with Object : его цель состоит в том, чтобы заменить скалярную или примитивную структуру (в данном случае, постоянный массив) объектом, где мы можем размещать методы, которые воздействуют на эти данные.

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

Обычно целью рефакторинга является ассоциативный массив, но он также может быть числовым с ограниченным числом элементов.

Когда вводить объект, где уже работает простой массив?

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

array(
    'FirstName LastName',
    'address@example.com'
)

Для ассоциативных массивов рефакторинг жизнеспособен каждый раз, когда количество элементов строго фиксировано:

array(
    'name' => ...
    'email' => ...
)

Закрытые поля являются самодокументируемыми, и их легче понять и поддерживать в документации по ключам массива. Документация по структурам массивов всегда повторяется в docblocks и не имеет реального места для жизни без класса; более того, это смерть инкапсуляции, поскольку ничто не мешает клиентскому коду (даже в тех частях, которые должны передавать массив другим методам) получить доступ к каждому элементу массива.

И, конечно, класс — это место для размещения методов , а массив не может их содержать.

меры

Техника, описанная Фаулером для этого рефакторинга, состоит из множества маленьких шагов:

  1. создайте новый класс : он должен содержать только открытое поле, немного инкапсулирующее массив.
  2. Измените клиентский код, чтобы использовать этот новый класс вместо примитивной переменной.
  3. В итерационном цикле добавьте метод get и метод set для каждого поля и измените код клиента. На каждом этапе должны быть проведены соответствующие тесты. Методы должны все еще использовать элементы массива.
  4. Когда эта фаза будет завершена, сделайте массив приватным и посмотрите, все ли работает код.
  5. Добавьте приватные поля, чтобы заменить элементы массива, и соответственно измените геттеры и сеттеры. Это изменение теперь распространяется только на исходный код нового класса.
  6. Когда вы закончите, удалите поле, хранящее массив.

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

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

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

пример

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

<?php
class ReplaceArrayWithObjectTest extends PHPUnit_Framework_TestCase
{
    public function testCanDefineAnHttpResponse()
    {
        $response = array(
            'success' => true,
            'content' => '{someJson:"ok"}'
        );
    }
}

Массив перемещается в открытое поле нового класса.

<?php
class ReplaceArrayWithObjectTest extends PHPUnit_Framework_TestCase
{
    public function testCanDefineAnHttpResponse()
    {
        $response = new HttpResponse(array(
            'success' => true,
            'content' => '{someJson:"ok"}'
        ));
    }
}

class HttpResponse
{
    public $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }
}

Мы добавляем сеттеры (также, если они нам нужны).

class HttpResponse
{
    public $data;

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function setSuccess($boolean)
    {
        $this->data['success'] = $boolean;
    }

    public function setContent($content)
    {
        $this->data['content'] = $content;
    }
}

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

<?php
class ReplaceArrayWithObjectTest extends PHPUnit_Framework_TestCase
{
    public function testCanDefineAnHttpResponse()
    {
        $response = new HttpResponse(array(
            'success' => true,
            'content' => '{someJson:"ok"}'
        ));
        $response->setSuccess(false);
        $response->setContent('{}');
        $this->assertEquals(new HttpResponse(array(
            'success' => false,
            'content' => '{}'
        )), $response);
    }
}

class HttpResponse
{
    private $data;

    public function __construct(array $data)
    {
        $this->setSuccess($data['success']);
        $this->setContent($data['content']);
    }

    public function setSuccess($boolean)
    {
        $this->data['success'] = $boolean;
    }

    public function setContent($content)
    {
        $this->data['content'] = $content;
    }
}

Закрытые поля заменяют элементы массива. Мы можем начать перемещать логику в методы нового класса.

class HttpResponse
{
    private $success;
    private $content;

    public function __construct(array $data)
    {
        $this->setSuccess($data['success']);
        $this->setContent($data['content']);
    }

    public function setSuccess($boolean)
    {
        $this->success = $boolean;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }
}