Статьи

Двойная рассылка: следующая лучшая вещь в отношении внедрения зависимостей

Объектно-ориентированные языки, такие как C ++, Java и PHP, реализуют то, что называется единой диспетчеризацией: когда у вас есть объект в переменной и вы вызываете метод для этой переменной, сообщение отправляется нужному классу во время выполнения, даже если когда Вы пишете код, который не хотите связывать вызовом с конкретной реализацией. Полиморфизм является примером этого механизма в действии.

Значение двойной отправки

Double Dispatch — простая идея: изменить поведение также в зависимости от класса вызывающего , а не только от вызываемого класса . Почему мы хотели бы сделать это?

Википедия приводит пример адаптивных алгоритмов столкновения, которые вычисляют результат столкновения между двумя объектами. Законы физики предписывают, что столкновение зависит от характеристик двух объектов, поэтому, когда мы сталкиваемся с Астероидом с Космическим кораблем, мы можем достичь другого результата, когда Астероид является экземпляром подкласса GiantAsteroid, или Космический корабль является экземпляр подкласса GiantSpaceship.

В более приземленном примере мы хотим что-то вроде этого:

class NotNullValidator
{
public function isValid(ValidableEntity $entity)
{
return $entity->isValid();
}
}
$validator = new NotNullValidator();
$entity = new User();
var_dump($validator->isValid());

чтобы произвести этот вывод:

User-specific code can be inserted here.
NotNullValidator-specific code can be inserted here.

При первоначальном дизайне мы можем достичь этого только путем добавления кода в класс NotNullValidator. Суть, однако, заключается в следующем: как мы можем создать метод User :: isValid (), который разрешается языком, который используется, когда сущность ValidableEntity $ оказывается пользователем, чтобы распознавать, что он был вызван NotNullValidator, а не чем-то еще? Мы хотим инкапсулировать этот «выбор вызывающего абонента» в класс User, например, потому что ему нужен доступ к закрытым полям пользователя, что определяет черту кода как принадлежащую классу User, а не кому-либо еще.

Реализация Double Dispatch

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

Даже в языках, где доступна перегрузка, таких как C, она обычно не выполняется динамически (например, для виртуальных методов), а вызываемый метод выбирается во время компиляции на основе ссылочной переменной. Это означает, что в C объект User, указанный обработчиком типа ValidableEntity, будет выбирать только метод, который принимает ValidableEntity в качестве аргумента для отправки, даже если доступны несколько перегруженных версий метода.

Как правило, ОО-языки поддерживают только одну диспетчеризацию : фактический вызываемый метод зависит от класса вызываемого объекта, но не может различить вызывающие. Как простой , я имею в виду отличается от хака , который включает трассировку стека. Поэтому Double Dispatch реализован через шаблон Visitor для большинства объектно-ориентированных языков.

Практическое использование

Когда Double Dispatch пригодится? Это общее решение для предоставления коллабораторов классам сущностей (например, пользователь, публикация, группа, статья …) модели предметной области.
Обычно мы не можем внедрить коллабораторов в такие классы, так как ORM не будет теперь, как воссоздавать объекты, если вы не настроите некоторый слушатель, который передает каждый восстановленный объект. Это верно в случае активных записей (даже если в этом случае теряется термин «сущность»), а также в том случае, если ORM является картографом данных. Класс User в этом примере может реализовать двойную диспетчеризацию: в этом случае одна диспетчеризация выполняется для переменной $ user, а другая — для коллаборатора, который передается в качестве параметра. Сотрудник также не должен быть вызывающим, он должен быть передан объекту в подписи метода, где это необходимо.

<?php
/**
* An Element in the Visitor pattern implementation.
*/
interface ValidableEntity
{
public function isValid(Validator $validator);
}

class User implements ValidableEntity
{
private $name = 'Giorgio';

public function isValid(Validator $validator)
{
echo "User-specific code can be inserted here.\n";
return $validator->validate(array('name' => $this->name));
}
}

/**
* Validators are Visitors, but implemented in the push style:
* the ValidableEntity only passes the necessary values to the Visitor.
*/
interface Validator
{
public function validate($values);
}

class NotNullValidator implements Validator
{
public function validate($values)
{
echo "NotNullValidator-specific code can be inserted here.\n";
foreach ($values as $fieldName => $eachValue) {
if ($eachValue === null) {
return false;
}
}
return true;
}

/**
* This is the entry point: we implement Double Dispatch by passing the caller
* object to the called one, so that the called can call a method on the original
* caller and take advatange of the method resolution supported by the language.
* The sum of two simple dispatches is a Double Dispatch. In the pattern's
* terminology, the Visitor is also the Client.
*/
public function isValid(ValidableEntity $entity)
{
return $entity->isValid($this);
}
}

$user = new User();
$validator = new NotNullValidator(/* collaborators can be injected here */);
var_dump($validator->isValid($user));

Преимущества

Конечно, в Double Dispatch нам не нужно вводить другие объекты в сущности , что, как я подумал, не является практикой проектирования, управляемой доменом. Внедрение также было бы не очень уместным, поскольку этот вид зависимостей используется только в одном или двух методах (как в примере для проверок, связанных с).

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

Entity может передать Посетителю только необходимые личные данные , так что его герметизация не нарушена. В этом примере пользователь передал значения своих приватных полей, которые нигде не должны отображаться. Более того, зависимость от коллаборатора хорошо видна : это сигнатура метода. Это шаг вперед от вставки нового Collaborator () в саму сущность.

Вывод

Единственным недостатком является то, что вызывающий объект сущности Api должен быть введен соавтором, чтобы иметь возможность передать его самому существу. Тем не менее, мы не можем полностью скрыть зависимость: ее нужно где-то внедрить , и при отсутствии доступного конструктора Entity, Double Dispatch дает нам стандартное решение в качестве следующего лучшего решения по отношению к DI.

Рекомендации:

http://en.wikipedia.org/wiki/Double_dispatch

http://misko.hevery.com/2009/07/31/how-to-think-about-oo/

http://c2.com/cgi/wiki?DoubleDispatch