В сегодняшнем сценарии два или более параметра часто передаются в набор похожих методов. Это происходит, например, с информацией о времени, содержащей день и месяц или часы и минуты; или, в других доменах, с параметрами, которые связаны друг с другом — такими как хост и порт ( google.com , 80 ), или изображение и его альтернативный текст.
Длинный список связанных параметров не обязательно означает, что метод делает слишком много. Сегодняшний рефакторинг — это решение, упрощающее сигнатуру и выражающее связь между текущими параметрами: оборачивая их в объект параметров.
Зачем писать код для нового класса?
Мы не должны бояться добавлять классы в том же темпе, в котором мы добавляем методы. Когда параметры почти всегда передаются вместе, они будут записываться вместе в каждой подписи и каждом вызове: это форма дублирования, и как таковая может и должна быть устранена.
Кроме того, дублирование не ограничивается одним методом : набор связанных аргументов может быть передан нескольким методам, каждый из которых вызывается во многих других местах, которые должны быть синхронизированы. Например, добавление параметра в исходной ситуации означает изменение множества различных исходных файлов.
Параметры часто являются признаком скрытого понятия: объект, моделирующий их объединение. Вы знаете, что находитесь на правильном пути, если название для этих концепций быстро появляется; в противном случае вам придется его изобрести.
Этот рефакторинг позволяет вам наложить больше логики на объект, представляющий этот набор значений , вместо того, чтобы передавать их в массиве (Primitive Obsession) или, как в этом случае, всегда вместе в виде нескольких параметров.
Конечным результатом также является более короткий и более понятный список параметров, который снова достигает цели упрощения вызовов методов, как мы делаем с большим количеством рефакторингов в этой части серии.
меры
- Создайте новый класс : экземпляр этого класса должен обернуть различные значения, которые передаются ему в конструкторе. Объект Parameter обычно будет объектом Value с неизменным состоянием и с геттерами для каждого из его полей (на данный момент).
- Выполните Добавить параметр, чтобы передать также новый объект (созданный на месте) вместе с различными параметрами.
- Для каждого из старых параметров примените к нему параметр «Удалить параметр» и получите доступ к нему внутри метода, вызвав метод получения объекта «Параметр».
Тесты всегда должны проходить между этапами. Последний шаг, включенный этим рефакторингом, состоит в перемещении логики на объект параметров. Если вам повезет, это даже приведет к исчезновению некоторых добытчиков.
пример
У нас есть объект 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); } }