Використання шаблону відвідувачів з великою ієрархією об'єктів


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);
      }
 }    

Я в основному бачу два варіанти:

  • Дотримуйтесь тієї ж конструкції, додаючи новий метод для моєї роботи до кожного похідного класу за рахунок ремонтопридатності (не варіант, IMHO)
  • Використовуйте "справжній" шаблон відвідувачів за рахунок розширюваності (не варіант, так як я очікую, що більше шляху буде впроваджено на шляху ...) з приблизно 50+ перевантаженнями методу відвідування, кожна з яких відповідає конкретній реалізації ?

Питання

Чи рекомендуєте ви використовувати шаблон відвідувача? Чи є якась інша закономірність, яка могла б допомогти вирішити це питання?


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.

Забезпечити реалізацію 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.