Comments 17
На самом деле неудобства наступают тогда, когда у вас объекты-данные, особенно если внешние или сгенерированные — добавление объекта вызывает изменения в нескольких местах. Когда количество таких объектов переваливает за первый десяток ( геометрия, например, или любая messaging-based система) — все это начинает вызывать ощутимый дискомфорт даже при чтении, не говоря уже про сопровождение.
Мы тоже используем подобный паттерн для обработки "сообщений" к актерам в Orleans например. И тоже перед этим прошли через стадии var x = input as Foo, и enum внутри объекта.
Я бы сказал что тут пример немного неудачный — паттерн хорошо работает когда у вас event-sourcing система, которая отправляет большое количество разных типов сообщений и бывает нужно получать сообщения не своего базового типа или даже просто иметь возможность очень быстро прикрутить новую обработку без изменения кода в 10 разных местах. Вот тогда этот паттерн очень помогает — просто заводите тип и пишете новый обработчик типа, остальное уже трогать не надо. А еще можно вкрутить централизованный логгинг, пре и пост-хуки и все это буквально в 1-2 линии кода
Например
public async Task ReceiveMsg(MessageEnvelopeBase item)
{
await validate(item as dynamic);
await preProcessing(item as dynamic);
await handle(item as dynamic);
await postProcessing(item as dynamic);
await logging(item as dynamic);
}
// тут определяем осмысленные методы для обработки базового класса MessageEnvelopeBase
// А потом по необходимости пишем обработчики валидаторы и хуки для конкретных сообщений,
// уже больше не трогая центральную точку входа ReceiveMsg(MessageEnvelopeBase item)
Не то что бы это какой-то rocket-science но код выглядит намного элегантнее. Да и разделение логики и данных хорошее — легко тестировать при помощи разных генераторов данных...
class Sanitize
{
...
public void Cleanup(object x)
{
GetType().GetMethod(nameof(Cleanup), new[] { x.GetType() })?.Invoke(this, x);
}
}
спасибо кэп) только вот не надо путать возможности системы типов и ООП. есть системы, где этого нет.
>В таких случаях я всегда применяю Visitor
круто! принцип timtowtdi должен быть всегда, но по теме?
причинно-следственную связь между параграфами держать не обязательно?
т.е. надо писать статью в виде твита: не используешь visitor,
>Не нужно в этом месте придумывать велосипед.
т.е. Вы назвали целый блок паттернов велосипедом. угу…
предлагаю на этом закончить дискуссию :)
вышеназванный шаблон прекрасно эмулируется через if, switch, visitor pattern в C, Java, C++ и т.д. даже reflection подойдет как крайний случай.
но обычно предполагается, что visitor является примером решения/частным случаем самого double dispatch и обычно используется для отделения данных от алгоритмов.
Типичным примером для visitor'a является (как Вы сами прекрасно знаете) обход какого-либо набора данных, скажем AST.
как и в случае с примером из статьи, вместо CollideWith — Visit/Accept. плюсом является использование принципа открытости/закрытости.
НО, visitor — это поведенческий шаблон, в то время как double/multiple dispatch являются такими же атрибутами полиморфизма как и параметрический полиморфизм (aka обобщения aka generics).
Ведь оба вводят понятие динамической диспетчеризации в пику single dispatch.
Почему double dispatch — частный случай multiple dispatch?
Потому что он (т.е. double dispatch) эмулирует последний (multiple dispatch), используя single dispatch. выбор перегрузки осуществляется на этапе компиляции, не так ли?
т.о., multiple dispatch является понятием более широким, чем double dispatch.
visitor, в свою очередь, является лишь примером решения/реализации double dispatch. в ситуации, когда сущности и их поведение сильно сплетены, то, увы, даже большой с натяжкой будет трудно это назвать visitor'ом. называть это можно будет как угодно, но double dispatch будет.
p.s.
надеюсь, что мы не потеряли еще одну важную деталь: double dispatch оперирует с 2-мя аргументами (где 1-й сам экземпляр, например), а multiple dispatch — со всеми аргументами метода, что только подтверждает умозаключения в статье.
Multiple dispatch в C#