Pull to refresh
Comments 17
верно подмечено! т.к. сам visitor относится к семейству шаблонов динамической диспетчеризации так же, как и double dispatch, то хотелось «разбавить» статью. visitor подошел бы тоже
А проблема N1 не слишком надуманна? В первом примере кода всё красиво. Выбирается метод с типом Node. Уже в методе можно разрулить какой именно тип в иерархии. Уже в самой Node потом написать внутренний virtual Cleanup(), чтобы каждый сам себя чистил как нужно.

На самом деле неудобства наступают тогда, когда у вас объекты-данные, особенно если внешние или сгенерированные — добавление объекта вызывает изменения в нескольких местах. Когда количество таких объектов переваливает за первый десяток ( геометрия, например, или любая 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 но код выглядит намного элегантнее. Да и разделение логики и данных хорошее — легко тестировать при помощи разных генераторов данных...

Почему не использовать Reflection?

class Sanitize
{
    ...

    public void Cleanup(object x)
    {
        GetType().GetMethod(nameof(Cleanup), new[] { x.GetType() })?.Invoke(this, x);
    }
}

вопрос же не в вызове гипотетического метода у объекта.
нужно: используя ссылку базового типа — выбрать правильную перегрузку.

reflection — не то что план B, а самый H :)
Приведение типов и то, что вы назвали ООП подход, по сути одно и тоже, просто if заменили на switch.
В таких случаях я всегда применяю Visitor. Не нужно в этом месте придумывать велосипед.
>просто if заменили на switch

спасибо кэп) только вот не надо путать возможности системы типов и ООП. есть системы, где этого нет.

>В таких случаях я всегда применяю Visitor
круто! принцип timtowtdi должен быть всегда, но по теме?
причинно-следственную связь между параграфами держать не обязательно?
т.е. надо писать статью в виде твита: не используешь visitor, тогда мы идем к вам, да? изобиловать выражениями вида «ну вы поняли».

>Не нужно в этом месте придумывать велосипед.
т.е. Вы назвали целый блок паттернов велосипедом. угу…
спасибо кэп) только вот не надо путать возможности системы типов и ООП. есть системы, где этого нет.

Вы же про C# пишите.
Суть в том, используете вы switch или if, не имеет значения. Т.к. у вас есть метод который знает о всех типах. Т.е. вы получили нерасширяемый дизайн (нарушили Open/closed principle)

т.е. Вы назвали целый блок паттернов велосипедом. угу…

Я имел ввиду, что в C# это делается визитором очень просто.
И в C# нет Multiple dispatch, поэтому используется Visitor (как бы вы его не называли, т.к. double dispatch в C# делается через Visitor)
>И в C# нет Multiple dispatch, поэтому используется Visitor (как бы вы его не называли, т.к. double dispatch в C# делается через Visitor)

предлагаю на этом закончить дискуссию :)
Если вы считаете, что с появлением dynamicв в C# появился Multiple dispatch, то да, думаю стоит закончить.
нет, я не считаю, что с появлением dynamic в C# появился multiple dispatch.

вышеназванный шаблон прекрасно эмулируется через 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 — со всеми аргументами метода, что только подтверждает умозаключения в статье.
Вы все правильно пишите.

Мой первый комментарий
Приведение типов и то, что вы назвали ООП подход, по сути одно и тоже, просто if заменили на switch.

был о том, что то, что вы назвали «приведене типов» и «ООП подход» — одно и тоже, т.к. даже в разделе «ООП» подход вы пользуетесь приведением типов.
Например вот:
sanitizer.Cleanup((Element)node);


И еще раз, я всего лишь хочу указать на то, что применяя приведение типов, или используя dynamic мы лишаемся главного преимущества C# — строгой типизации.

И, лично для меня, «элегантность кода» важна меньше, чем строгая типизация.

Я считаю использование dynamic плохой практикой, т.к. ни разу не видел хорошего его применения (кроме работы с Word/Excel), а вот как dynamic используют для workaround видел и не однажды.
ага! поинт понятен теперь. ну да, от перестановки слагаемых сумма не меняется ;)

p.s.
наверное, имели в виду статическую типизацию, ибо тот же Python — язык динамический, но со строгой типизацией.
наверное, имели в виду статическую типизацию

Да, именно статическую типизацию, спасибо.
Only those users with full accounts are able to leave comments. Log in, please.