Использование шаблона посетителя с большой иерархией объектов


12

контекст

Я использовал с иерархией объектов (дерево выражений) «псевдо» шаблон посетителя (псевдо, так как в нем не используется двойная диспетчеризация):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Этот дизайн был, однако, сомнительным, довольно удобным, так как количество реализаций MyInterface является значительным (~ 50 или более), и мне не нужно было добавлять дополнительные операции.

Каждая реализация уникальна (это другое выражение или оператор), а некоторые являются составными (т. Е. Узлами операторов, которые будут содержать другие операторы / конечные узлы).

Обход в настоящее время выполняется путем вызова операции Accept на корневом узле дерева, которая, в свою очередь, вызывает Accept на каждом из его дочерних узлов, что, в свою очередь ... и так далее ...

Но пришло время, когда мне нужно добавить новую операцию , например, красивую печать:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Я в основном вижу два варианта:

  • Сохраняйте тот же дизайн, добавляя новый метод для моей операции к каждому производному классу, за счет удобства обслуживания (не вариант, ИМХО)
  • Используйте «истинный» шаблон Visitor за счет расширяемости (не вариант, так как я ожидаю, что в будущем будет реализовано больше реализаций ...), с более чем 50-ю перегрузками метода Visit, каждая из которых соответствует определенной реализации ?

Вопрос

Вы бы порекомендовали использовать шаблон Visitor? Есть ли другой шаблон, который может помочь решить эту проблему?


1
Возможно, цепь декораторов будет более подходящей?
MattDavey

некоторые вопросы: как эти реализации отличаются? какова структура иерархии? и всегда ли это одна и та же структура? вам всегда нужно пройти через структуру в том же порядке?
JK.

@MattDavey: так что вы бы порекомендовали иметь один декоратор для реализации и операции?
Т. Фабр

2
@ T.Fabre трудно сказать. Существует более 50 разработчиков MyInterface. Все ли эти классы имеют уникальную реализацию DoSomethingи DoSomethingElse? Я не вижу, где ваш класс посетителей фактически пересекает иерархию - facadeв данный момент это больше похоже на ...
MattDavey

и какая версия C # это. у тебя есть лямбды? или linq? в вашем распоряжении
JK.

Ответы:


13

В течение более 10 лет я использовал шаблон посетителей для представления деревьев выражений в шести крупномасштабных проектах на трех языках программирования, и я очень доволен результатом. Я нашел несколько вещей, которые сделали применение шаблона намного проще:

Не используйте перегрузки в интерфейсе посетителя

Поместите тип в имя метода, т.е. используйте

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

скорее, чем

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Добавьте метод «поймать неизвестного» в интерфейс вашего посетителя.

Это позволило бы пользователям, которые не могут изменить ваш код:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Это позволило бы им создавать свои собственные реализации IExpressionи IVisitor"понимать" их выражения, используя информацию о типах во время выполнения при реализации их VisitExpressionметода catch-all .

Обеспечить реализацию IVisitorинтерфейса по умолчанию, ничего не делающую

Это позволило бы пользователям, которым необходимо иметь дело с подмножеством типов выражений, быстрее создавать своих посетителей и сделать свой код невосприимчивым к добавлению вами новых методов IVisitor. Например, написание посетителя, который собирает все имена переменных из ваших выражений, становится легкой задачей, и код не сломается, даже если вы добавите несколько новых типов выражений к себе IVisitorпозже.


2
Можете ли вы уточнить, почему вы говорите Do not use overloads in the interface of the visitor?
Стивен Эверс

1
Можете ли вы объяснить, почему вы не рекомендуете использовать перегрузки? Я где-то читал (на oodesign.com, на самом деле), что на самом деле не имеет значения, использую ли я перегрузки или нет. Есть ли какая-то конкретная причина, почему вы предпочитаете этот дизайн?
Т. Фабр

2
@ T.Fabre Это не имеет значения с точки зрения скорости, но это имеет значение с точки зрения читабельности. Разрешение метода на двух из трех языков, на которых я это реализовал ( Java и C #), требует шага во время выполнения для выбора среди возможных перегрузок, что делает код с огромным количеством перегрузок немного сложнее для чтения. Рефакторинг кода также становится проще, потому что выбор метода, который вы хотите изменить, становится тривиальной задачей.
dasblinkenlight

@SnOrfus Пожалуйста, смотрите мой ответ T.Fabre выше.
dasblinkenlight

@dasblinkenlight C # теперь предлагает динамическое решение, позволяющее среде выполнения решать, какой перегруженный метод следует использовать (не во время компиляции). Есть ли еще причина, почему бы не использовать перегрузку?
Tintenfiisch
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.