Статьи

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

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

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

Метод перемещения

Первый из этих рефакторингов также самый простой: метод перемещения между классами. Предположение о том, что вы хотите переместить метод M класса А на другой класс B . Старый метод может либо исчезнуть, либо на время превратиться в делегирование новому.

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

Есть несколько практических правил, которые могут помочь вам принять решение о движении:

  • переместите M в B, потому что B содержит данные, которые использует M : цель состоит в том, чтобы улучшить сцепление.
  • Переместите M в B, потому что вы не хотите, чтобы объекты, вызывающие M, зависели от A , но если они зависят от B, картина упрощается. Цель состоит в том, чтобы уменьшить связь и устранить неудобную зависимость.

меры

Перемещение метода M из класса A в класс B требует следующих шагов.

  1. Проверьте, что использует M: возможно, придется переместить и другие методы .
  2. Отметьте A суперклассы и подклассы : они могут помешать вам перемещать метод без рефакторинга «больше, чем вы хотите», если они содержат переопределенные или переопределяющие версии M.
  3. Объявляет метод в B . Вы можете изменить его имя, если оно избыточно с метаданными B, такими как его пространство имен или имя класса.
  4. Скопируйте код из A в B : если метод интенсивно использует A, передайте его как параметр и замените его на $ this. Измените также выброшенные исключения, если они есть, и механизм захвата отличается (например, перемещение входа между слоями подразумевает разные исключения).
  5. Ссылка на объект B из A путем инъекции или создания экземпляра . Это изменение не обязательно должно быть постоянным, и во многих случаях уже есть ссылка, которую можно использовать.
  6. Превратите старый метод в делегирование .
  7. Запустите тесты на нужном уровне детализации . Модульные тесты, вероятно, нуждаются в обновлении перед переносом, по крайней мере, на этапе аранжировки.

Дополнительным шагом вы можете воспользоваться, удалив оригинальный метод А. В этом случае вызывающие абоненты M должны быть обновлены в получаемой ссылке (экземпляр B вместо экземпляра A) и подписи (которые могут отличаться из-за добавления объекта A, где это необходимо.)

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

пример

Первоначально у этого класса TagCloud есть некоторая зависть к классу Link, поскольку он выполняет работу, которую можно выполнить в классе Link, без введения странных внешних зависимостей (например, объекта службы i18n).

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

<?php
class MoveMethodTest extends PHPUnit_Framework_TestCase
{
public function testDisplayItsLinksInShortForm()
{
$tagCloud = new TagCloud(array(
new Link('http://giorgiosironi.blogspot.com/search/label/productivity'),
new Link('http://giorgiosironi.blogspot.com/search/label/software%20development')
));
$html = $tagCloud->toHtml();
$this->assertEquals(
"<a href=\"http://giorgiosironi.blogspot.com/search/label/productivity\">productivity</a>\n"
. "<a href=\"http://giorgiosironi.blogspot.com/search/label/software%20development\">software development</a>\n",
$html
);
}
}

class TagCloud
{
private $links;

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

public function toHtml()
{
$html = '';
foreach ($this->links as $link) {
$text = $this->linkText($link);
$html .= "<a href=\"$link\">$text</a>\n";
}
return $html;
}

private function linkText(Link $link)
{
$lastFragment = substr(strrchr($link, '/'), 1);
return str_replace('%20', ' ', $lastFragment);
}
}

class Link
{
private $url;

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

public function __toString()
{
return $this->url;
}
}

После перемещения метод становится проще, поскольку он не должен пересекать границу класса.

class TagCloud
{
private $links;

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

public function toHtml()
{
$html = '';
foreach ($this->links as $link) {
$text = $link->text();
$html .= "<a href=\"$link\">$text</a>\n";
}
return $html;
}
}

class Link
{
private $url;

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

public function __toString()
{
return $this->url;
}

/**
* The movement change the method in many ways:
* - simpler name
* - public visibility
* - one less parameter
* The refactoring makes Link a behavior-rich Value Object, but this is
* incidental; if I could have moved a method that used an external
* service from Link to TagCloud. However, in these samples I have a
* limited space and often many of my objects are Value Objects just
* because I wanted to post running examples, and they are a really
* portable kind of code.
*/
public function text()
{
$lastFragment = substr(strrchr($this, '/'), 1);
return str_replace('%20', ' ', $lastFragment);
}
}