Итак, в моей предыдущей статье мы увидели, как мы можем сохранить инкапсуляцию - один из самых фундаментальных принципов объектно-ориентированного дизайна (OOD) - «поместив поведение там, где находятся данные».

Но теперь, когда мы действительно начинаем использовать эти красиво инкапсулированные объекты, мы сталкиваемся с проблемой, которая кажется проблемой, и нам, в конце концов, потребуется доступ к тем свойствам, которые мы так аккуратно инкапсулировали. Почему? Что ж, мы хотим предоставить нашему известному клиенту / пользователю подробную информацию о том, сколько денег у них есть на их карте. И как они, черт возьми, собираемся это делать, если у нас нет доступа к внутренним компонентам объекта Card ??

Наша первая программа, пытающаяся использовать нашу модель, выглядит так:

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

echo 'You have £' . $card->getBalance()->getAmount() . ' left on your card';

Но почти сразу же вы сталкиваетесь с ужасным осознанием того, что ваша Карта не имеет геттеров (и что еще хуже - класса Money). Черт, нам придется снова добавить этот дурацкий получатель на нашу карту и нарушить его инкапсуляцию, иначе как мы сможем получить доступ к его балансу?

Нет. Подожди минутку. Опять же, мы застряли в нашем императивном образе мышления. Давайте попробуем еще раз применить поговорку «поместите поведение туда, где данные». Мы дадим Card метод printBalance (). Ура! Теперь нам не нужен getBalance (), потому что мы выполняем печать внутри класса Card:

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

public function format(string $format): string
{
    return sprintf($format, $this->amount);
}

А теперь мы можем переписать наш метод printBalance в классе Card следующим образом:

public function printBalance()
{
    $message = sprintf('You have £%s on your card', $this->balance->format('%.2f'));

    echo $message . "\n";
}

Однако это по-прежнему проблематично по двум причинам:
1. как распечатать баланс в классе Card, хотя на самом деле он не принадлежит там
2. Мы жестко запрограммировали устройство вывода на stdout

В идеале мы хотим иметь возможность отделить работу как распечатать баланс от что для печати в виде сообщения, и для этого нам нужно либо ввести другой класс обслуживания в качестве зависимости (например, создать Card с классом Stdout) или создать экземпляр Stdout в нашем файле main.php и сделайте что-нибудь вроде

$stdout = new Stdout();
$stdout->print($card);

Первое решение: вставить Stdout в конструктор Card ( вместе с объектом начального значения Деньги) является проблематичным, не в последнюю очередь потому, что, на языке DDD, наша Карта является «юридическим лицом», а Stdout - «услугой». И, если вы что-то знаете о DDD, вы можете знать, что каждый раз, когда вы вводите услугу в сущность, Бог убивает котенка. Возможно, вас не слишком заботят котята или DDD, но по причинам, которые я не буду здесь вдаваться, вам не следует этого делать.

Последнее решение не нарушает никаких правил DDD, но по-прежнему требует от класса Stdout доступа к балансу карты и, следовательно, нарушает его инкапсуляцию.

Что делать?

Двойная отправка на помощь!

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

Напомним, наша цель - иметь возможность отображать баланс на Карте, не нарушая инкапсуляцию или убивая котят. И как это работает:

Мы создаем экземпляр нашего объекта Stdout (который реализует интерфейс OutputDevice) следующим образом:

$outputDevice = new StdOut();

Затем мы передаем этот объект в наш метод Card объектов printBalance () и используем этот метод из Объект Card - и теперь наши main.php и Card класс выглядят так (потерпите меня, станет ясно, почему мы делаем это в момент):

Когда возникает «двойная отправка», это вызов метода print () на OutputDevice, который вызывается внутри Класс Card (где у нас есть доступ к карточкам баланс и где мы создаем сообщение, которое хочет видеть клиент) . Принимая во внимание, что реализация Stdout содержит только логику, необходимую для вывода строки в stdout (и конечно, теперь мы также можем легко заменить Stdout на Принтер или MobileDevice или любое другое устройство, на которое клиент хотел бы, чтобы сообщение было распечатано на ):

Итак, теперь мы не только успешно сохранили инкапсуляцию наших объектов в неприкосновенности, но и смогли распределить обязанности в нужном месте:

Карта отправляет сообщение о своем балансе, которое отправляется на OutputDevice, а наш main.php отправляет сообщение на нашу Карту. приказывая ему распечатать баланс на выбранном нами устройстве вывода.