Этот рефакторинг является специализацией Replace Data Value with Object : его цель состоит в том, чтобы заменить скалярную или примитивную структуру (в данном случае, постоянный массив) объектом, где мы можем размещать методы, которые воздействуют на эти данные.
Мы уже видели облегченную версию этого рефакторинга в примере кода этой статьи: на этот раз мы переходим к реальному объекту, который имеет закрытые поля, представляющие элементы массива.
Обычно целью рефакторинга является ассоциативный массив, но он также может быть числовым с ограниченным числом элементов.
Когда вводить объект, где уже работает простой массив?
Подсказка, указывающая на необходимость такого рефакторинга в числовых массивах, заключается в том, что элементы не являются однородными: они могут быть по типу (все строки или целые числа), но не по смыслу. Например, если два или они перевернуты, массив теряет смысл или становится очень странным:
array( 'FirstName LastName', '[email protected]' )
Для ассоциативных массивов рефакторинг жизнеспособен каждый раз, когда количество элементов строго фиксировано:
array( 'name' => ... 'email' => ... )
Закрытые поля являются самодокументируемыми, и их легче понять и поддерживать в документации по ключам массива. Документация по структурам массивов всегда повторяется в docblocks и не имеет реального места для жизни без класса; более того, это смерть инкапсуляции, поскольку ничто не мешает клиентскому коду (даже в тех частях, которые должны передавать массив другим методам) получить доступ к каждому элементу массива.
И, конечно, класс — это место для размещения методов , а массив не может их содержать.
меры
Техника, описанная Фаулером для этого рефакторинга, состоит из множества маленьких шагов:
- создайте новый класс : он должен содержать только открытое поле, немного инкапсулирующее массив.
- Измените клиентский код, чтобы использовать этот новый класс вместо примитивной переменной.
- В итерационном цикле добавьте метод get и метод set для каждого поля и измените код клиента. На каждом этапе должны быть проведены соответствующие тесты. Методы должны все еще использовать элементы массива.
- Когда эта фаза будет завершена, сделайте массив приватным и посмотрите, все ли работает код.
- Добавьте приватные поля, чтобы заменить элементы массива, и соответственно измените геттеры и сеттеры. Это изменение теперь распространяется только на исходный код нового класса.
- Когда вы закончите, удалите поле, хранящее массив.
Многие маленькие шаги часто являются подходящими, поскольку использование массива охватывает десятки различных классов и повышает риск достижения непоправимо нарушенной сборки.
После того, как вы достигли конечного состояния, объекта с геттерами и сеттерами, вы можете продолжать и удалять методы для неизменности или инкапсуляции; или перенести Иностранные Методы в новый класс теперь, когда он стал гражданином первого класса.
Обратите внимание, что тесты могут охватывать даже сквозные тесты, если массив использовался в больших масштабах. Например, мы заменили массивы объектами в двух верхних слоях приложения, заставив нас запускать тесты в полном масштабе.
пример
В исходном состоянии ответ создается путем объединения массива. Код клиента опущен для краткости, и только часть создания будет нашей целью.
<?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; } }