Дмитpий Hecтepук

Блог о программировании — C#, F#, C++, архитектура, и многое другое

Паттерн Visitor и Dynamic

5 комментариев

В 22й части нашего подкаста, мы упомянули про то, что паттерн Visitor упростился в .Net 4 благодаря “динамикам”. Для тех кто хочет объяснения – пишу этот пост.

Представим что у нас есть этакая структура:

abstract class Person
{
  public IList<Person> Children { get; set; }
}
class Man : Person {
  ...
}
class Woman : Person {
  ...
}

Тогда в классической реализации, для “посещения” всего дерева мы бы писали нечто подобное:

class Visitor
{
  void Process(Person person)
  {
    foreach (Person p in person.Children) {
      if (p is Man)
        VisitMan(p as Man);
      else
        VisitWoman(p as Woman);
    }
  }
  void VisitMan(Map m)
  {
    ...
  }
  void VisitWoman(Woman w)
  {
    ...
  }
}

Это коряво и “не по паттернам”. В 4м .Net можно сделать полиморфную перегрузку метода Visit так:

void Visit(Man m) { ... }
void Visit(Woman w) { ... }

А чтобы все это заработало, меняем код на следующий:

public void Process(Person person)
{
  foreach (dynamic p in person.Children)
    Visit(p);
}

Ключевое слово dynamic позволит вам вызвать правильную перегрузку метода. Важно – если вы забудете реализовать Visit() для какого-то типа, получите исключение в рантайме!

Реклама

Written by Dmitri

25 сентября 2010 в 17:33

Опубликовано в .NET, C#

комментариев 5

Subscribe to comments with RSS.

  1. Это МЕГА! Меня всегда напрягало, что не получается такой «полиморфизм сбоку» устроить. А теперь, оказывается, можно! Значит, и с Extension методами такая штука получится?

    ulu

    26 сентября 2010 at 19:42

    • Эмм, ну в принципе как только кастуешь объект в dynamic, получаешь такое поведение. В моем примере это implicit через foreach, но никто не мешает делать ((dynamic)obj).SomeMethod().

      Dmitri

      26 сентября 2010 at 23:44

  2. Дмитрий, а Вы здесь рассматриваете классический Визитор или что-то свое? Дело в том, что если брать классический паттерн визитор с двойной диспетчеризацией — то не надо этих вкусностей языка. Обработка класса Персон должна происходить внутри класса Персон, а не в Визиторе. В этом случае, любой child у Person будет выполнять accept(visitor) и внутри вызывать конкретный VisitMan() или VisitWoman(). В вышеприведенном примере теряется сама суть двойной диспетчеризации…
    P.S. Кстати сами визиторы так и называют — СДЕЛАЙ-ЧТО-ТО-Visitor. А конкретные методы внутри знают что и как делать для принимаемых параметров. Т.е. в общем случае, в визиторе не должны быть публичные методы, которые делают что-то отличное от приема конкретного экземпляра класса и действий над ним (действие задается в названии визитора).
    P.P.S Вы вышесказанное актуально для классического визитора. Если у Вас свой — тогда все вышеуказанное — не имеет отношения к предмету рассуждения.

    Евгений

    26 сентября 2010 at 22:29

    • Я мог бы переписать мой пример, добавив double dispatch. Идея остается прежней – вместо того чтобы писать visitor.VisitMan(this), мы пишем visitor.Visit(this). А теперь можно развить идею – например, вынести метод Accept() в базовый класс (если все классы имеют общего родителя). Если нет – реализуем поведенческую примесь поверх marker-интерфейса.

      Dmitri

      26 сентября 2010 at 23:55

  3. Вот именно. Соль Визитора — это double dispatch. Без него это что-то другое. Хотя идея, безусловно, мне понравилась.

    Евгений

    27 сентября 2010 at 7:50


Оставить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: