Статьи

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

Объект Client вызывает метод M1 или создает значение некоторым эквивалентным способом. Затем он передает результат новому методу M2 на объекте Server. Тем не менее, сервер может искать метод самостоятельно, из-за некоторой ссылки, которую он имеет в своих полях или через другие параметры.

Упрощением для этого сценария является рефакторинг « Заменить параметр методом» : вы можете удалить вызов M1; или, лучше сказать, вы можете переместить этот вызов внутри M2.

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

Краткий пример кода, показывающий только механизм этого рефакторинга:

$partial = $object->calculate();
$serverObject->finish($partial);
// becomes
$serverObject->finish(); //internally calls $object

Предположения

Предположение, которое мы делаем для применения рефакторинга, заключается в том, что Сервер может достичь M1: в противном случае вместо передачи результата операции вам нужно будет просто передать весь объект, на котором находится M1 (это будет экземпляр Preserve Whole Object в факт.)

Зачем использовать вызов метода?

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

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

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

меры

В качестве предварительного условия извлеките в методе расчет параметра в случае, если он сложный. Если это просто поле, которое передается или доступно, не беспокойтесь об этом; но если объект Server должен потратить 10 строк, чтобы найти нужный объект, извлеките этот код.

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

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

Тесты должны проходить после каждого шага.

пример

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

Мы отображаем текст различных сообщений с помощью метода __toString () потока. Каждый пост также сообщает о предыдущем как цитату.

<?php
class ReplaceParameterWithMethod extends PHPUnit_Framework_TestCase
{
    public function test()
    {
        $thread = new Thread();
        $thread->add(new Post('Hello...'));
        $thread->add(new Post('Hi!'));
        $this->assertEquals("Hello...\n\n> In reply to: Hello...\nHi!\n\n", $thread->__toString());
    }
}

class Thread
{
    private $posts = array();
    private $lastPost = null;

    /**
     * Sets up a linked list of Post objects in addition to the array.
     * We assume this kind of traversal is already in place for other reasons
     * or collaborations between Post objects.
     */
    public function add(Post $post) 
    {
        $this->posts[] = $post;
        $post->setOrigin($this->lastPost);
        $this->lastPost = $post;
    }

    /**
     * This method contains mechanics that duplicate the linked list.
     */
    public function __toString()
    {
        $previousPost = null;
        $text = '';
        foreach ($this->posts as $post) {
            $text .= $post->toString($previousPost);
            $text .= "\n";
            $previousPost = $post;
        }
        return $text;
    }
}

class Post
{
    private $text;
    private $origin;

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

    public function setOrigin(Post $post = null)
    {
        $this->origin = $post;
    }

    public function toString(Post $previousPost = null) {
        if ($previousPost) {
            $text = '> In reply to: ' . $previousPost->text . "\n";
        } else {
            $text = '';
        }
        $text .= $this->text . "\n";
        return $text;
    }
}

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

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

class Post
{
    private $text;
    private $origin;

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

    public function setOrigin(Post $post = null)
    {
        $this->origin = $post;
    }

    public function toString(Post $previousPost = null) {
        if ($this->origin) {
            $text = '> In reply to: ' . $this->origin->text . "\n";
        } else {
            $text = '';
        }
        $text .= $this->text . "\n";
        return $text;
    }
}

Теперь мы можем применить Удалить параметр. Код клиента на этот раз — не тест, а класс Thread. (Мы также можем переименовать метод в __toString (), но это второстепенный момент, хотя он включается этим рефакторингом.)

<?php
class ReplaceParameterWithMethod extends PHPUnit_Framework_TestCase
{
    public function test()
    {
        $thread = new Thread();
        $thread->add(new Post('Hello...'));
        $thread->add(new Post('Hi!'));
        $this->assertEquals("Hello...\n\n> In reply to: Hello...\nHi!\n\n", $thread->__toString());
    }
}

class Thread
{
    private $posts = array();
    private $lastPost = null;

    /**
     * Sets up a linked list of Post objects in addition to the array.
     * We assume this kind of traversal is already in place for other reasons
     * or collaborations between Post objects.
     */
    public function add(Post $post) 
    {
        $this->posts[] = $post;
        $post->setOrigin($this->lastPost);
        $this->lastPost = $post;
    }

    /**
     * This method contains mechanics that duplicate the linked list.
     */
    public function __toString()
    {
        $previousPost = null;
        $text = '';
        foreach ($this->posts as $post) {
            $text .= $post->__toString();
            $text .= "\n";
            $previousPost = $post;
        }
        return $text;
    }
}

class Post
{
    private $text;
    private $origin;

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

    public function setOrigin(Post $post = null)
    {
        $this->origin = $post;
    }

    public function __toString() {
        if ($this->origin) {
            $text = '> In reply to: ' . $this->origin->text . "\n";
        } else {
            $text = '';
        }
        $text .= $this->text . "\n";
        return $text;
    }
}

Тест все еще проходит.

[13:42:15][giorgio@Desmond:~/Dropbox/practical-php-refactoring]$ phpunit ReplaceParameterWithMethod.php
PHPUnit 3.5.15 by Sebastian Bergmann.

.

Time: 0 seconds, Memory: 3.50Mb

OK (1 test, 1 assertion)