Статьи

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

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

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

Зачем писать код для нового класса?

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

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

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

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

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

меры

  1. Создайте новый класс : экземпляр этого класса должен обернуть различные значения, которые передаются ему в конструкторе. Объект Parameter обычно будет объектом Value с неизменным состоянием и с геттерами для каждого из его полей (на данный момент).
  2. Выполните Добавить параметр, чтобы передать также новый объект (созданный на месте) вместе с различными параметрами.
  3. Для каждого из старых параметров примените к нему параметр «Удалить параметр» и получите доступ к нему внутри метода, вызвав метод получения объекта «Параметр».

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

пример

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

<?php
class IntroduceParameterObject extends PHPUnit_Framework_TestCase
{
    public function testTaxesInformationDriveTheQuotationsTotalAndTypeFields()
    {
        $quotation = new Quotation();
        $quotation->addRow(1000);
        $quotation->specifyTaxes(20, 'VATCODE');
        $this->assertEquals(1200, $quotation->getTotal());
        $this->assertEquals('Type: VATCODE', $quotation->getTypeOfService());
    }
}

class Quotation
{
    private $netTotal = 0;
    private $vatPercentage;
    private $vatCode;

    public function addRow($row)
    {
        // including just the logic to implement the test we have
        $this->netTotal += $row;
    }

    public function specifyTaxes($percentage, $code)
    {
        $this->vatPercentage = $percentage;
        $this->vatCode = $code;
    }

    public function getTotal()
    {
        return $this->netTotal * (1 + $this->vatPercentage / 100); 
    }

    public function getTypeOfService()
    {
        return 'Type: ' . $this->vatCode;
    }
}

Мы замечаем две вещи:

  • один в остальной части кода (здесь не показан): 20% -ый процент и связанный с ним код передаются во многие методы . Это имеет смысл, так как разные коды (хлеб против автомобилей) могут означать разные проценты налога.
  • в самом классе поля, хранящие налоговую информацию, имеют очень похожие имена : $ vatRate и $ vatCode. Это мягкая форма дублирования.

Итак, мы хотим преобразовать пару параметров в объект параметров. Мы создаем класс, который нам нужен:

class VatRate
{
    private $rate;
    private $code;

    public function __construct($rate, $code)
    {
        $this->rate = $rate;
        $this->code = $code;
    }

    public function getRate()
    {
        return $this->rate;
    }

    public function getCode()
    {
        return $this->code;
    }
}

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

<?php
class IntroduceParameterObject extends PHPUnit_Framework_TestCase
{
    public function testTaxesInformationDriveTheQuotationsTotalAndTypeFields()
    {
        $quotation = new Quotation();
        $quotation->addRow(1000);
        $quotation->specifyTaxes(new VatRate(20, 'VATCODE'), 20, 'VATCODE');
        $this->assertEquals(1200, $quotation->getTotal());
        $this->assertEquals('Type: VATCODE', $quotation->getTypeOfService());
    }
}

class Quotation
{
    private $netTotal = 0;
    private $vatRate;
    private $vatPercentage;
    private $vatCode;

    public function addRow($row)
    {
        // including just the logic to implement the test we have
        $this->netTotal += $row;
    }

    public function specifyTaxes(VatRate $vatRate, $percentage, $code)
    {
        $this->vatRate = $vatRate;
        $this->vatPercentage = $percentage;
        $this->vatCode = $code;
    }

    public function getTotal()
    {
        return $this->netTotal * (1 + $this->vatPercentage / 100); 
    }

    public function getTypeOfService()
    {
        return 'Type: ' . $this->vatCode;
    }
}

Объекты счетов-фактур имеют всю необходимую им информацию, доступную из экземпляра VatRate. Итак, давайте удалим $ процент, первый параметр, заключенный в объект параметров:

class Quotation
{
    private $netTotal = 0;
    private $vatRate;
    private $vatPercentage;
    private $vatCode;

    public function addRow($row)
    {
        // including just the logic to implement the test we have
        $this->netTotal += $row;
    }

    public function specifyTaxes(VatRate $vatRate, $code)
    {
        $this->vatRate = $vatRate;
        $this->vatCode = $code;
    }

    public function getTotal()
    {
        return $this->netTotal * (1 + $this->vatRate->getRate() / 100); 
    }

    public function getTypeOfService()
    {
        return 'Type: ' . $this->vatCode;
    }
}

И теперь мы также можем удалить $ code, другой параметр:

class Quotation
{
    private $netTotal = 0;
    private $vatRate;

    public function addRow($row)
    {
        // including just the logic to implement the test we have
        $this->netTotal += $row;
    }

    public function specifyTaxes(VatRate $vatRate)
    {
        $this->vatRate = $vatRate;
    }

    public function getTotal()
    {
        return $this->netTotal * (1 + $this->vatRate->getRate() / 100); 
    }

    public function getTypeOfService()
    {
        return 'Type: ' . $this->vatRate->getCode();
    }
}

Мы замечаем, что этот рефакторинг позволил сделать еще один шаг: перенести расчет налога в VatRate. Это также означает, что мы можем удалить метод getRate ().

class Quotation
{
    private $netTotal = 0;
    private $vatRate;

    public function addRow($row)
    {
        // including just the logic to implement the test we have
        $this->netTotal += $row;
    }

    public function specifyTaxes(VatRate $vatRate)
    {
        $this->vatRate = $vatRate;
    }

    public function getTotal()
    {
        return $this->vatRate->tax($this->netTotal);
    }

    public function getTypeOfService()
    {
        return 'Type: ' . $this->vatRate->getCode();
    }
}

class VatRate
{
    private $rate;
    private $code;

    public function __construct($rate, $code)
    {
        $this->rate = $rate;
        $this->code = $code;
    }

    public function getCode()
    {
        return $this->code;
    }

    public function tax($netAmount)
    {
        return $netAmount * (1 + $this->rate / 100); 
    }
}