Статьи

Практический рефакторинг PHP: дублирование наблюдаемых данных

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

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

Чем сложнее становятся правила обновления, тем больше их нужно помещать на уровень домена, а не на верхние. С такими понятиями доменов, как скидки и налоги, они со временем станут более сложными.

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

Не совсем Наблюдатель

Если вы знаете изречение «Не повторяйте себя», вы также знаете, что все дублированное должно быть синхронизировано. В исходном намерении шаблон Observer должен быть установлен между объектами домена и уровнем представления; но в первоначальном намерении этот рефакторинг нацелен на настольные приложения.

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

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

меры

Этапы PHP-версии отличаются от классических, описанных Фаулером. 

  1. Введите несколько дублированных полей в классы домена, имитируя те, которые уже существуют в презентации.
  2. Скопируйте логику для их вычисления в доменных классах; Вы можете проверить эту логику на уровне модулей, и теперь будет еще проще, если результаты не будут сгруппированы форматированием, кодировкой JSON или тегами HTML.
  3. Заменить логику операцией копирования в презентационных классах; Копирование уже должно быть на месте для большей части данных, так что нужно просто сделать больше. Возможно, вам придется ввести методы получения или эквивалентные методы в классах домена.

пример

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

<?php
class DuplicateObservedData extends PHPUnit_Framework_TestCase
{
    public function testTheTotalMustBeDisplayed()
    {
        $invoice = new Invoice();
        $invoice->addRow(100);
        $invoice->addRow(50);
        $invoiceView = new InvoiceView($invoice);
        // simplified representation to avoid cluttering this example with HTML
        $this->assertEquals("100\n50\n---\n150", $invoiceView->__toString());
    }
}

class Invoice
{
    private $rows = array();

    public function addRow($amount)
    {
        $this->rows[] = $amount;
    }

    public function getRows()
    {
        return $this->rows;
    }
}

class InvoiceView
{
    private $rows;

    public function __construct(Invoice $invoice)
    {
        $this->rows = $invoice->getRows();
    }

    public function __toString()
    {
        return implode("\n", $this->rows)
             . "\n---\n"
             . array_sum($this->rows);
    }
}

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

class Invoice
{
    private $rows = array();

    public function addRow($amount)
    {
        $this->rows[] = $amount;
    }

    public function getRows()
    {
        return $this->rows;
    }

    public function getTotal()
    {
        return array_sum($this->rows);
    }
}

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

class InvoiceView
{
    private $rows;
    private $total;

    public function __construct(Invoice $invoice)
    {
        $this->rows = $invoice->getRows();
        $this->total = $invoice->getTotal();
    }

    public function __toString()
    {
        return implode("\n", $this->rows)
             . "\n---\n"
             . $this->total;
    }
}