Коли я повинен використовувати шаблон дизайну відвідувачів? [зачинено]


315

Я постійно бачу посилання на шаблон відвідувачів у блогах, але я повинен визнати, я просто не розумію цього. Я читаю статтю у Вікіпедії щодо викрійки, і розумію її механіку, але все ще плутаюсь щодо того, коли я буду її використовувати.

Як хтось нещодавно дійсно отримав шаблон декоратора і зараз бачить його використання абсолютно скрізь, я хотів би мати можливість інтуїтивно зрозуміти і цей, здавалося б, зручний малюнок.


7
Нарешті отримав це, прочитавши цю статтю Джермі Міллера на моїй ожині, поки застряг, чекаючи у холі дві години. Це довго, але дає чудове пояснення подвійної диспетчеризації, відвідування та складання, і що ви можете зробити з цим.
Джордж Мауер


3
Шаблон відвідувачів? Який? Справа в тому, що навколо цього дизайнерського шаблону є багато непорозумінь і чиста плутанина. Я написав і статтю, яка, сподіваюсь, наводить певний порядок у цьому хаосі: rgomes-info.blogspot.co.uk/2013/01/…
Річард Гомес

Коли ви хочете мати об’єкти функцій для типів даних об'єднання, вам знадобиться шаблон відвідувачів. Вам може бути цікаво, що таке об’єкти функцій та типи даних об'єднання, тоді варто прочитати ccs.neu.edu/home/matthias/htdc.html
Wei Qiu

Приклади тут і тут .
jaco0646

Відповіді:


315

Я не дуже знайомий із шаблоном відвідувачів. Подивимось, чи правильно я це зрозумів. Припустимо, у вас є ієрархія тварин

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(Припустимо, це складна ієрархія з налагодженим інтерфейсом.)

Тепер ми хочемо додати нову операцію до ієрархії, а саме ми хочемо, щоб кожна тварина звучала. Наскільки ієрархія така проста, ви можете це зробити за допомогою прямого поліморфізму:

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

Але, діючи таким чином, кожен раз, коли ви хочете додати операцію, ви повинні змінювати інтерфейс для кожного класу ієрархії. Тепер припустімо, що ви задоволені оригінальним інтерфейсом і хочете внести в нього найменші зміни.

Шаблон Visitor дозволяє переміщувати кожну нову операцію у відповідному класі, і вам потрібно розгорнути інтерфейс ієрархії лише один раз. Давай зробимо це. Спочатку ми визначаємо абстрактну операцію (клас "Відвідувач" в GoF ), яка має метод для кожного класу в ієрархії:

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

Потім ми змінюємо ієрархію, щоб прийняти нові операції:

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

Нарешті, ми реалізуємо фактичну операцію, не змінюючи ні Кішки, ні Собаки :

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

Тепер у вас є спосіб додавати операції без зміни ієрархії. Ось як це працює:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

19
С.Лотт, ходити по дереву - це насправді не відвідувач. (Це "ієрархічний шаблон відвідувачів", який заплутано зовсім інший.) Немає способу показати шаблон GoF Visitor без використання успадкування чи реалізації інтерфейсу.
чудовий

14
@Knownasilya - Це неправда. & -Operator надає адресу Sound-Object, яка потрібна інтерфейсу. letsDo(Operation *v) потрібен вказівник.
AquilaRapax

3
тільки для наочності, чи правильний цей приклад дизайну відвідувачів?
godzilla

4
Після багато роздумів, мені цікаво, чому ви назвали два методи тутIsADog та hereIsACat, хоча ви вже передаєте Собаку та Кішку методам. Я вважаю за краще простий performTask (Object * obj), і ви кидаєте цей об'єкт у клас Operation. (і мовою, яка підтримує переосмислення, немає потреби в кастингу)
Abdalrahman Shatou

6
У вашому "головному" прикладі наприкінці: ви theSound.hereIsACat(c)зробили б роботу, як ви виправдовуєте всі накладні витрати, введені шаблоном? подвійне відправлення - це виправдання.
franssu

131

Причиною вашої плутанини, мабуть, є те, що Відвідувач - фатальний помилок. Багато (відомих 1 !) Програмістів натрапили на цю проблему. Що насправді робить, це здійснити подвійну диспетчеризацію мовами, які не підтримують її на самому світі (більшість з них не робить).


1) Мій улюблений приклад - Скотт Мейєрс, відомий автор "Ефективного С ++", який назвав це одним із найважливіших своїх аліментів C ++! моменти коли-небудь .


3
+1 "немає шаблону" - ідеальна відповідь. найвигідніша відповідь доводить, що багато програмістів c ++ ще не усвідомлюють обмеження віртуальних функцій над "adhoc" поліморфізмом за допомогою типу enum і case case (шлях c). Використовувати віртуальний може бути акуратно і непомітно, але він все ще обмежений однією відправленням. На мою особисту думку, це найбільший недолік c ++.
user3125280

@ user3125280 Я читав 4/5 статей та розділ "Шаблони дизайну" про шаблон відвідувачів, і жодна з них не пояснює перевагу використання цього неясного шаблону над випадком stmt, або коли ви можете використовувати одну над іншою. Thx хоча б виховуючи це!
шпигун

4
@ Сем , я впевнений , що вони пояснюють це - це те ж саме перевага , що ви завжди отримуєте від підкласів / виконання поліморфізму через switch: switchжорсткі коди прийняття рішень на стороні клієнта (код дублювання) і не забезпечує статичну перевірку типів ( перевірити комплектність та виразність справ тощо). Шаблон відвідувача перевіряється засобом перевірки типів і, як правило, робить код клієнта більш простим.
Конрад Рудольф

@KonradRudolph дякую за це. Зауважуючи, однак, його адресу не стосується, зокрема, у Шаблонах або статті Вікіпедії. Я не погоджуюсь з вами, але ви можете стверджувати, що є користь використання Stmt case також, тому його дивним є, як правило, не протиставлення: 1. Вам не потрібен метод accept () для об'єктів вашої колекції. 2. ~ відвідувач може обробляти об'єкти невідомого типу. Таким чином, випадок stmt здається кращим придатним для роботи над об'єктними структурами із змінною колекцією типів, що беруть участь. Шаблони визнають, що шаблон відвідувача недостатньо підходить до такого сценарію (p333).
шпигун

1
Місце спондера @SamPinkus Konrad - саме тому virtualподібні функції настільки корисні в сучасних мовах програмування - вони є основним складовим елементом розширюваних програм - на мою думку, спосіб c (вкладений перемикач або відповідність шаблону тощо, залежно від мови вибору) - набагато чистіший код, який не потребує розширення, і я був приємно здивований, побачивши цей стиль у складному програмному забезпеченні, як доказ 9. Більш важливо, що будь-яка мова, яка хоче забезпечити розширення, повинна, ймовірно, вміщувати кращі схеми відправки, ніж рекурсивна разова відправка (тобто відвідувач).
user3125280

84

Тут все правильно, але я думаю, що не вдається вирішити питання "коли". По-перше, із шаблонів дизайну:

Відвідувач дозволяє визначити нову операцію без зміни класів елементів, на яких вона працює.

Тепер давайте подумаємо про просту ієрархію класів. У мене є класи 1, 2, 3 і 4 і методи A, B, C і D. Викладіть їх, як у електронній таблиці: класи - це рядки, а методи - стовпці.

Тепер, об’єктно-орієнтований дизайн передбачає, що ви швидше вирощуєте нові класи, ніж нові методи, тому додавати більше рядків, так би мовити, простіше. Ви просто додаєте новий клас, вказуєте, що відрізняється в цьому класі, і успадковує решту.

Іноді, хоча, класи відносно статичні, але вам потрібно часто додавати більше методів - додавання стовпців. Стандартним способом в дизайні ОО було б додати такі методи до всіх класів, що може бути дорого. Візерунок відвідувачів робить це легко.

До речі, це проблема, яку має вирішити узгоджений зразок Scala.


Чому я використовую шаблон відвідувачів лише для класу ultity. я можу назвати свій клас утиліти так: AnalyticsManger.visit (someObjectToVisit) проти AnalyticsVisitor.visit (деякийOjbectToVisit). Яка різниця ? вони обоє роблять розлуку, чи не так? сподіваюся, ви можете допомогти.
j2emanue

@ j2emanue Оскільки шаблон відвідувача використовує правильну перевантаження відвідувача під час виконання. У той час як ваш код потребує кастингу, щоб викликати правильне перевантаження.
Доступ заборонено

Чи є підвищення ефективності з цим? Я здогадуюсь, що це дозволяє уникнути гарної ідеї
j2emanue

@ j2emanue ідея полягає в написанні коду, який відповідає принципу відкритого / закритого типу, а не причин продуктивності. Дивіться відкритий закритий у дядька Боба butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
доступ заборонено

22

Відвідувачі шаблон дизайну працює дуже добре для «рекурсивних» структур , таких як дерева каталогів, XML - структури, або обриси документів.

Об'єкт Visitor відвідує кожен вузол рекурсивної структури: кожен каталог, кожен тег XML і все, що завгодно. Об'єкт Visitor не проходить цикл через структуру. Натомість методи Visitor застосовуються до кожного вузла структури.

Ось типова рекурсивна структура вузла. Може бути каталог або тег XML. [Якщо ваша людина на Java, уявіть собі багато додаткових методів створення та підтримки списку дітей.]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

visitМетод застосовується об'єкт відвідувачів до кожного вузла в структурі. У цьому випадку це відвідувач зверху вниз. Ви можете змінити структуру visitметоду, щоб виконати замовлення знизу вгору чи якесь інше замовлення.

Ось суперклас для відвідувачів. Він використовується visitметодом. Він "надходить" на кожен вузол структури. Оскільки visitметод викликає upі down, відвідувач може стежити за глибиною.

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

Підклас може робити такі речі, як підрахунок вузлів на кожному рівні та накопичувати список вузлів, генеруючи приємні номери ієрархічних розділів шляху.

Ось додаток. Він будує структуру дерева, someTree. Це створює Visitor, dumpNodes.

Потім воно застосовується dumpNodesдо дерева. dumpNodeОб'єкт буде «відвідати» кожен вузол в дереві.

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

Алгоритм TreeNode visitгарантує, що кожен TreeNode використовується як аргумент arrivedAtметоду відвідувача .


8
Як зазначають інші, це "ієрархічна схема відвідувачів".
PPC-Coder

1
@ PPC-Coder У чому полягає відмінність між "ієрархічною схемою відвідувачів" та схемою відвідувачів?
Тім Ловелл-Сміт

3
Ієрархічний шаблон відвідувачів є більш гнучким, ніж класичний шаблон відвідувачів. Наприклад, за ієрархічним малюнком ви можете відстежувати глибину проходу і вирішувати, яку гілку потрібно пройти або зупинити проїзд усіх разом. Класичний відвідувач не має цієї концепції і відвідає всі вузли.
PPC-Coder

18

Один із способів поглянути на це полягає в тому, що шаблон відвідувачів - це спосіб дозволити вашим клієнтам додавати додаткові методи для всіх ваших класів у певній ієрархії класів.

Це корисно, коли у вас досить стабільна ієрархія класів, але у вас є змінні вимоги, що потрібно робити з цією ієрархією.

Класичний приклад - для компіляторів тощо. Абстрактне дерево синтаксису (AST) може точно визначити структуру мови програмування, але операції, які ви хочете зробити на AST, змінюватимуться в міру просування вашого проекту: генератори коду, симпатичні принтери, налагоджувачі, аналіз метрики складності.

Без шаблону відвідувачів кожен раз, коли розробник захотів додати нову функцію, їм потрібно було б додати цей метод до кожної функції базового класу. Це особливо важко, коли базові класи з’являються в окремій бібліотеці або створюються окремою командою.

(Я чув, як він стверджував, що схема відвідувачів суперечить правильній практиці ОО, оскільки вона переміщує операції з даними від даних. Шаблон відвідувача корисний саме в тому випадку, коли звичайні практики OO не вдається.)


Я також хотів би вашої думки щодо наступного: Чому я використовую шаблон відвідувачів лише для класу ultity. я можу назвати свій клас утиліти так: AnalyticsManger.visit (someObjectToVisit) проти AnalyticsVisitor.visit (деякийOjbectToVisit). Яка різниця ? вони обоє роблять розлуку, чи не так? сподіваюся, ви можете допомогти.
j2emanue

@ j2emanue: Я не розумію питання. Я пропоную вам розім'яти це і поставити його як повне запитання для кожного, хто відповість.
Відмінна думка

1
я розмістив новий питання тут: stackoverflow.com/questions/52068876 / ...
j2emanue

14

Існує щонайменше три дуже вагомі причини використання шаблону відвідувачів:

  1. Зменшити розповсюдження коду, що лише незначно відрізняється, коли змінюються структури даних.

  2. Застосовуйте однакові обчислення до декількох структур даних, не змінюючи код, який реалізує обчислення.

  3. Додайте інформацію до застарілих бібліотек, не змінюючи застарілого коду.

Погляньте, будь ласка, статтю, яку я написав про це .


1
Я прокоментував вашу статтю з найбільшою користю, яку я бачив для відвідувачів. Думки?
Джордж Мауер

13

Як вже зазначав Конрад Рудольф, він підходить для тих випадків, коли нам це потрібно подвійна відправка

Ось приклад, щоб показати ситуацію, коли нам потрібна подвійна відправка, і як відвідувач допомагає нам у цьому.

Приклад:

Скажімо, у мене є 3 типи мобільних пристроїв - iPhone, Android, Windows Mobile.

На всіх цих трьох пристроях в них встановлено радіо Bluetooth.

Давайте припустимо, що радіо синій зуб може бути від двох окремих оригіналів - Intel & Broadcom.

Для того, щоб зробити приклад релевантним для нашої дискусії, давайте також припустимо, що API, викриті радіо Intel, відрізняються від тих, які піддаються радіо Broadcom.

Ось так виглядають мої заняття -

введіть тут опис зображення введіть тут опис зображення

Тепер я хотів би запровадити операцію - Увімкнення Bluetooth на мобільному пристрої.

Підпис її функції повинен мати щось подібне -

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

Отже, залежно від правильного типу пристрою та залежно від правильного типу радіо Bluetooth , його можна вмикати, викликаючи відповідні кроки або алгоритм .

В основному, це стає матрицею 3 х 2, де я намагаюся векторувати правильну операцію залежно від потрібного типу об'єктів.

Поліморфна поведінка залежно від типу обох аргументів.

введіть тут опис зображення

Тепер до цієї проблеми можна застосувати шаблон відвідувачів. Натхнення походить зі сторінки Вікіпедії, в якій зазначається: «По суті, відвідувач дозволяє додавати нові віртуальні функції до групи класів без зміни самих класів; натомість створюється клас відвідувачів, який реалізує всі відповідні спеціалізації віртуальної функції. Відвідувач приймає посилання на екземпляр як вхідний і реалізує мету шляхом подвійної відправки. "

Подвійна відправка тут є необхідністю завдяки матриці 3x2

Ось як виглядатиме налаштування - введіть тут опис зображення

Я написав приклад, щоб відповісти на інше запитання, тут згадується код та його пояснення .


9

Мені було легше за такими посиланнями:

У http://www.remondo.net/visitor-pattern-example-csharp/ я знайшов приклад, який показує прикладний приклад, який показує, яка користь для відвідувачів. Тут ви маєте різні класи контейнерів для Pill:

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

Як ви бачите вище, ви BilsterPackмістять пари таблеток ', тому вам потрібно помножити кількість пар на 2. Також ви можете помітити, що Bottleвикористання, unitяке є іншим типом даних, і яке потрібно віддати.

Отже, в основному методі ви можете розрахувати кількість таблеток, використовуючи наступний код:

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

Зауважте, що вищезгаданий код порушує Single Responsibility Principle. Це означає, що ви повинні змінити основний код методу, якщо ви додасте новий тип контейнера. Також зробити перемикання довше - це погана практика.

Отже, ввівши наступний код:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Ви перенесли відповідальність за підрахунок числа Pills до класу, який називається PillCountVisitor(І ми видалили випадок case switch). Це означає, що коли вам потрібно додати новий тип контейнера для таблеток, слід змінити лише PillCountVisitorклас. Також IVisitorінтерфейс повідомлень є загальним для використання в інших сценаріях.

Додавши метод Accept для класу контейнерів для таблеток:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

ми дозволяємо відвідувачу відвідувати класи контейнерів з таблетками.

В кінці ми обчислюємо кількість таблеток, використовуючи наступний код:

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

Це означає: кожен контейнер з таблетками дозволяє PillCountVisitorвідвідувачеві бачити їх кількість таблеток. Він знає, як рахувати таблетки.

На значення visitor.Countмає таблетки.

У http://butunclebob.com/ArticleS.UncleBob.IuseVisitor ви бачите реальний сценарій, у якому не можна використовувати поліморфізм (відповідь), щоб слідувати принципу єдиної відповідальності. Насправді в:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

reportQtdHoursAndPayметод для подання та подання , і це порушує єдину відповідальність Принципу. Тому краще використовувати шаблон відвідувачів для подолання проблеми.


2
Привіт, Сайеде, чи можете ви відредагуйте свою відповідь, щоб додати частини, які вас вважають найяскравішими. Так, як правило, не відволікає відповіді, що стосуються лише посилань, оскільки мета полягає у створенні бази даних знань, а посилання знижуються.
Джордж Мауер

8

Подвійна відправка - лише одна з причин використання цієї моделі .
Але зауважте, що єдиний спосіб реалізації подвійної або більше диспетчеризації мовами використовує єдину парадигму диспетчеризації.

Ось причини використання шаблону:

1) Ми хочемо визначати нові операції, не змінюючи модель щоразу, тому що модель не змінюється часто, коли хижі операції змінюються часто.

2) Ми не хочемо поєднувати модель та поведінку, тому що ми хочемо мати багаторазову модель у кількох програмах або ми хочемо мати розширювану модель яка дозволяє класам клієнтів визначати їх поведінку за допомогою власних класів.

3) У нас є загальні операції, які залежать від конкретного типу моделі, але ми не хочемо реалізовувати логіку в кожному підкласі, оскільки це вибухне загальною логікою в декількох класах і так у багатьох місцях .

4) Ми використовуємо модель доменної моделі, а класи моделей однієї ієрархії виконують дуже багато різних речей, які можна було б зібрати десь в іншому місці .

5) Нам потрібна подвійна відправка .
У нас є змінні, задекларовані з типами інтерфейсів, і ми хочемо мати можливість обробляти їх відповідно до їх типу виконання ... звичайно, без використання if (myObj instanceof Foo) {}чи жодної хитрості.
Ідея полягає, наприклад, передати ці змінні методам, які оголошують конкретний тип інтерфейсу як параметр для застосування певної обробки. Такий спосіб зробити неможливо, якщо ящик з мовами покладається на одноразове відправлення, оскільки вибраний режим, який викликається під час виконання, залежить лише від типу часу виконання приймача.
Зауважте, що в Java метод (підпис) для виклику вибирається під час компіляції, і це залежить від оголошеного типу параметрів, а не від їх типу виконання.

Останній пункт, який є приводом для використання відвідувача, також є наслідком, оскільки, коли ви реалізуєте відвідувача (звичайно, для мов, які не підтримують багаторазову доставку), вам обов'язково потрібно запровадити подвійну диспетчерську реалізацію.

Зауважте, що обхід елементів (ітерація) для застосування відвідувача на кожному з них не є приводом для використання шаблону.
Ви використовуєте шаблон, тому що розділяєте модель та обробляєте.
А використовуючи шаблон, ви отримуєте перевагу додатково від ітераторської здатності.
Ця здатність дуже потужна і виходить за межі ітерації на звичайний тип із конкретним методом, як accept()це загальний метод.
Це особливий випадок використання. Тож я перекладу це на один бік.


Приклад на Java

Я проілюструю додаткову цінність шаблону на шаховому прикладі, де ми хотіли б визначити обробку, як гравець вимагає переміщення твору.

Без використання шаблону відвідувачів ми могли б визначити рухоме поведінку безпосередньо в підкласах.
У нас може бути, наприклад, Pieceінтерфейс, такий як:

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

Кожен підклас Piece реалізував би його, наприклад:

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

І те саме для всіх підкласів Piece.
Ось клас діаграм, який ілюструє цей дизайн:

[модельна схема класу

Цей підхід має три важливі недоліки:

- поведінка , таке як , performMove()або computeIfKingCheck()буде дуже ймовірно , використовувати загальну логіку.
Наприклад, що б конкретно не було Piece, performMove()нарешті встановить поточний шматок у певному місці та потенційно візьме частину суперника.
Розщеплення пов'язаних форм поведінки на кілька класів, а не збирання їх перемагає певним чином єдину схему відповідальності. Посилення їх ремонту.

- обробка як checkMoveValidity()не повинна бути чимось, що Pieceпідкласи можуть бачити або змінювати.
Це перевірка, що виходить за межі людських чи комп’ютерних дій. Ця перевірка виконується під час кожної дії, яку вимагає гравець, щоб гарантувати, що запитуваний хід дійсний.
Тому ми навіть не хочемо цього надавати в Pieceінтерфейсі.

- У шахових іграх, які є складними для розробників-ботів, програма, як правило, пропонує стандартний API ( Pieceінтерфейси, підкласи, дошка, загальна поведінка тощо) та дозволяють розробникам збагачувати свою бот-стратегію.
Для того, щоб зробити це, ми повинні запропонувати модель, коли дані та поведінка не є щільно сполученими в Pieceреалізаціях.

Тож переходимо до використання шаблону відвідувачів!

У нас є два види структури:

- модельні класи, які приймають відвідувати (шматки)

- відвідувачі, які їх відвідують (рухомі операції)

Ось схема класу, яка ілюструє візерунок:

введіть тут опис зображення

У верхній частині маємо відвідувачів, а в нижній - класи моделей.

Ось PieceMovingVisitorінтерфейс (поведінка, визначена для кожного виду Piece):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

Штука визначена зараз:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

Його ключовий метод:

void accept(PieceMovingVisitor pieceVisitor);

Він забезпечує першу відправлення: виклик на основі Pieceприймача.
Під час компіляції метод пов'язаний з accept()методом інтерфейсу Piece, а під час виконання обмежений метод буде викликаний у Pieceкласі виконання .
І саме accept()реалізація методу виконає другу відправлення.

Дійсно, кожен Pieceпідклас, який хоче відвідувати PieceMovingVisitorоб'єкт, викликає PieceMovingVisitor.visit()метод, передаючи сам аргумент.
Таким чином компілятор обмежує, як тільки час компіляції, тип оголошеного параметра з конкретним типом.
Є друга відправка.
Ось Bishopпідклас, який ілюструє, що:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

І ось приклад використання:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

Недоліки відвідувачів

Шаблон відвідувачів - дуже потужний шаблон, але він також має деякі важливі обмеження, які слід враховувати перед його використанням.

1) Ризик зменшити / порушити інкапсуляцію

При деяких видах операцій шаблон відвідувача може зменшити або порушити інкапсуляцію об'єктів домену.

Наприклад, оскільки MovePerformingVisitor класі потрібно встановити координати фактичного фрагмента, Pieceінтерфейс повинен передбачити спосіб:

void setCoordinates(Coordinates coordinates);

Відповідальність за Pieceзміни координат тепер відкрита для інших класів, ніж Pieceпідкласи.
Переміщення обробки, виконаної відвідувачем, в Pieceпідкласи також не є варіантом.
Це дійсно створить ще одну проблему, оскільки Piece.accept()приймає будь-яку реалізацію відвідувачів. Він не знає, що відвідувач виконує, і тому немає уявлення про те, чи можна змінити стан Шматка.
Способом ідентифікації відвідувача було б виконати обробку публікації Piece.accept()відповідно до реалізації відвідувача. Було б дуже погана ідея , оскільки це дозволить створити високе зчеплення між реалізаціями Публічні і штучних підкласів і , крім того, ймовірно , буде потрібно використовувати трюк getClass(), instanceofабо будь-який маркер , що ідентифікує реалізацію відвідувачів.

2) Вимога щодо зміни моделі

На відміну від деяких інших моделей поведінкового дизайну, як, Decoratorнаприклад, модель відвідувачів є нав'язливою.
Нам дійсно потрібно змінити початковий клас приймача, щоб надати accept()метод прийняття до відвідування.
У нас не було жодної проблеми Pieceта її підкласів, оскільки це наші класи .
У вбудованих або сторонніх заняттях справи не такі прості.
Нам потрібно обернути або успадкувати (якщо зможемо) їх, щоб додати accept()метод.

3) Непрямі

Візерунок створює кратні непрямі.
Подвійна відправка означає два виклики замість одного:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

І ми можемо мати додаткові непрямі, оскільки відвідувач змінює стан відвідуваного об'єкта.
Це може виглядати як цикл:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)

6

Кей Хорстманн має чудовий приклад, куди слід застосувати відвідувача у своїй книзі OO Design and pattern . Він узагальнює проблему:

Складені об’єкти часто мають складну структуру, складену з окремих елементів. Деякі елементи можуть знову мати дочірні елементи. ... Операція над елементом відвідує його дочірні елементи, застосовує до них операцію та поєднує результати. ... Однак додати нові операції до такої конструкції непросто.

Причина це непросто - це те, що операції додаються в самі структури класів. Наприклад, уявіть, що у вас є файлова система:

Діаграма класів FileSystem

Ось кілька операцій (функціональних можливостей), які ми могли б хотіти реалізувати з цією структурою:

  • Відображення імен елементів вузла (список файлів)
  • Показати обчислений розмір елементів вузла (де розмір каталогу включає розмір усіх його дочірніх елементів)
  • тощо.

Ви можете додати функції до кожного класу у FileSystem для реалізації операцій (а люди робили це раніше, оскільки це дуже очевидно, як це зробити). Проблема полягає в тому, що щоразу, коли ви додаєте нову функціональність (рядок "і т.д." вище), вам може знадобитися додавати все більше і більше методів до структурних класів. В якийсь момент, після деякої кількості операцій, які ви додали до свого програмного забезпечення, методи в цих класах вже не мають сенсу з точки зору функціональної згуртованості класів. Наприклад, у вас є FileNodeметод, який має метод calculateFileColorForFunctionABC()для реалізації останньої функціональності візуалізації у файловій системі.

Шаблон відвідувачів (як і багато моделей дизайну) народився від болю та страждань розробників, які знали, що існує кращий спосіб дозволити їхньому коду змінюватися, не вимагаючи багато змін скрізь, а також дотримуючись хороших принципів дизайну (висока згуртованість, низька зв’язність ). На мою думку, важко зрозуміти корисність багатьох моделей, поки ти не відчув цього болю. Пояснення болю (як ми намагаємося зробити вище з функціями "тощо", які додаються) займає місце в поясненні і є відволіканням. З цієї причини зрозуміти закономірності важко.

Відвідувач дозволяє нам відокремити функціональні можливості структури даних (наприклад, FileSystemNodes) від самих структур даних. Шаблон дозволяє дизайну поважати згуртованість - класи структури даних простіші (у них менше методів), а також функціональні можливості вкладені у Visitorреалізацію. Це робиться за допомогою подвійної диспетчеризації (що є складною частиною шаблону): використання accept()методів у класах структури та visitX()методів у класах Visitor (функціональність):

Діаграма класів FileSystem із застосованим відвідувачем

Ця структура дозволяє нам додавати нові функціональні можливості, які працюють на структурі як конкретні відвідувачі (без зміни класів структури).

Діаграма класів FileSystem із застосованим відвідувачем

Наприклад, a, PrintNameVisitorякий реалізує функціональність переліку каталогів, і a, PrintSizeVisitorщо реалізує версію з розміром. Ми могли б собі уявити, як одного дня з'явиться "ExportXMLVisitor", який генерує дані в XML, або інший відвідувач, який генерує їх у JSON тощо. Ми могли навіть мати відвідувача, який відображає моє дерево каталогів за допомогою графічної мови, наприклад DOT , для візуалізації. з іншою програмою.

На завершення: Складність відвідувача з його подвійною розсилкою означає, що це важче зрозуміти, кодувати і налагоджувати. Коротше кажучи, він має високий коефіцієнт видовищності і втрачає принцип KISS. У опитуванні, проведеному дослідниками, відвідувач показав суперечливий зразок (не було єдиної думки щодо його корисності). Деякі експерименти навіть показали, що це не полегшує підтримку коду.


Структура каталогів, на мою думку, є хорошою складовою схемою, але згоден з вашим останнім абзацом.
зар

5

На мою думку, обсяг роботи над додаванням нової операції є більш-менш однаковим з використанням Visitor Patternабо прямої модифікації кожної елементної структури. Крім того, якщо я маю додати новий клас елементів, скажімо Cow, інтерфейс Operation буде впливати на це, і це поширюється на всі існуючі класи елементів, тому вимагає перекомпіляції всіх класів елементів. Тож який сенс?


4
Майже кожен раз, коли я використовував Visitor, коли ви працюєте з проходженням ієрархії об'єктів. Розгляньте вкладене меню дерева. Ви хочете згортати всі вузли. Якщо ви не реалізуєте відвідувача, вам слід написати код переходу графіків. Або з відвідувачем : rootElement.visit (node) -> node.collapse(). З відвідувачем кожен вузол реалізує обхід графіків для всіх своїх дітей, так що ви закінчили.
Джордж Мауер

@GeorgeMauer, концепція подвійної відправки очистила мотивацію для мене: або логіка, що залежить від типу, залежить від типу, або - від болю. Ідея розподілу логіки обходу все ще дає мені паузу. Це ефективніше? Це більш рентабельне? Що робити, якщо в якості вимоги буде додано "згортання на рівень N"?
nik.shornikov

Ефективність @ nik.shornikov насправді тут не повинна турбувати. Майже на будь-якій мові кілька викликів функцій є незначними накладними. За межами цього є мікрооптимізація. Це більш рентабельне? Ну, це залежить. Я думаю, що це найбільше, іноді це не так. Що стосується "складання на рівень N". Легкий перехід у levelsRemainingлічильник як параметр. Зменшіть його перед тим, як викликати дітей наступного рівня. Всередині вашого відвідувача if(levelsRemaining == 0) return.
Джордж Мауер

1
@GeorgeMauer, повністю погодився з ефективністю, що є незначною проблемою. Але, на мою думку, рішення повинно зводитись до ремонту, наприклад, відміни підпису акцепту.
nik.shornikov

5

Шаблон відвідувачів як та ж підпільна реалізація для програмування Aspect Object ..

Наприклад, якщо ви визначаєте нову операцію без зміни класів елементів, на яких вона працює


на згадку про програмування
об'єктних аспектів

5

Короткий опис моделі відвідувачів. Усі класи, які потребують модифікації, повинні реалізувати метод 'accept'. Клієнти називають цей метод прийняття виконувати деяку нову дію над цією сімейством класів, тим самим розширюючи їх функціональність. Клієнти можуть використовувати цей метод прийняття для виконання широкого спектру нових дій, переходячи в інший клас відвідувачів для кожної конкретної дії. Клас відвідувачів містить кілька перекритих методів відвідування, що визначають, як досягти того самого конкретного дії для кожного класу в сім'ї. Ці методи відвідування передаються екземпляру, над яким потрібно працювати.

Коли ви можете розглянути можливість його використання

  1. Коли у вас є сім'я класів, ви знаєте, що вам доведеться додати всі нові дії всім, але ви чомусь не в змозі змінити або перекомпілювати сім'ю класів у майбутньому.
  2. Коли ви хочете додати нову дію і мати цю нову дію повністю визначеною в одному класі відвідувачів, а не розбиватися по декількох класах.
  3. Коли ваш начальник каже, що ви повинні створити цілий ряд занять, які повинні робити щось зараз ! ... але ніхто насправді точно не знає, що це щось ще є.

4

Я не зрозумів цю закономірність, поки не зіткнувся зі статтею дядька Боб і прочитав коментарі. Розглянемо наступний код:

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

Хоча це може виглядати добре, оскільки воно підтверджує єдину відповідальність, але воно порушує принцип відкритості та закритості . Кожен раз, коли у вас є новий тип працівника, вам доведеться додавати, якщо за допомогою перевірки типу. І якщо ви цього не зробите, ви ніколи не дізнаєтесь про це під час компіляції.

За допомогою шаблону відвідувачів ви можете зробити свій код чистішим, оскільки він не порушує принципу відкритого / закритого типу та не порушує єдиної відповідальності. І якщо ви забудете реалізувати візит, він не складеться:

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

Магія полягає в тому, що хоча він v.Visit(this)виглядає однаково, він насправді відрізняється, оскільки викликає різні перевантаження відвідувачів.


Так, я спеціально вважаю, що це корисно при роботі зі структурами дерев, а не лише плоскими списками (плоскі списки були б особливим випадком дерева). Як ви зазначаєте, його не дуже безладно лише у списках, але відвідувач може бути рятівником, оскільки навігація між вузлами стає складнішою
Джордж Мауер

3

На основі чудової відповіді @Federico A. Ramponi.

Уявіть собі, що у вас є така ієрархія:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

Що станеться, якщо вам потрібно додати тут метод «Прогулянки»? Це буде боляче для всієї конструкції.

У той же час додавання методу «Прогулянки» породжує нові запитання. А як щодо "Їжте" чи "Сон"? Чи повинні ми дійсно додати новий метод до ієрархії тварин для кожної нової дії чи операції, яку ми хочемо додати? Це потворно і найголовніше, що ми ніколи не зможемо закрити інтерфейс Animal. Отже, за допомогою шаблону відвідувачів ми можемо додати новий метод до ієрархії без зміни ієрархії!

Отже, просто перевірте та запустіть цей приклад C #:

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}

ходити, їсти не є придатними прикладами, оскільки вони спільні як для, так Dogі для Cat. Ви могли зробити їх у базовому класі, щоб вони успадковувались або обирали відповідний приклад.
Абхінав Гауніял

звуки відрізняються тхо, хороший зразок, але не впевнений, чи має це щось спільне з малюнком відвідувачів
DAG

3

Відвідувач

Відвідувач дозволяє додавати нові віртуальні функції до сімейства класів без зміни самих класів; натомість створюється клас відвідувачів, який реалізує всі відповідні спеціалізації віртуальної функції

Структура відвідувачів:

введіть тут опис зображення

Використовуйте шаблон відвідувача, якщо:

  1. Подібні операції необхідно виконувати на об'єктах різних типів, згрупованих у структурі
  2. Потрібно виконати багато чітких і неспоріднених операцій. Він відокремлює операцію від структури об'єктів
  3. Нові операції потрібно додавати без зміни структури об'єкта
  4. Зберіть пов'язані операції в один клас, а не змушуйте вас змінювати або виводити класи
  5. Додайте функції до бібліотек класів, для яких або у вас немає джерела, або ви не можете змінити джерело

Навіть незважаючи на те, що шаблон відвідувача забезпечує гнучкість для додавання нових операцій без зміни існуючого коду в Object, ця гнучкість має недолік.

Якщо новий об’єкт Visitable був доданий, він вимагає змін коду в класах Visitor & ConcreteVisitor . Існує рішення для вирішення цього питання: Використовуйте рефлексію, яка вплине на продуктивність.

Фрагмент коду:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

Пояснення:

  1. Visitable( Element) - це інтерфейс, і цей метод інтерфейсу потрібно додати до набору класів.
  2. Visitorце інтерфейс, який містить способи виконання операції над Visitableелементами.
  3. GameVisitorце клас, який реалізує Visitorінтерфейс ( ConcreteVisitor).
  4. Кожен Visitableелемент приймає Visitorі викликає відповідний метод Visitorінтерфейсу.
  5. Ви можете ставитися Gameяк Elementі до конкретних ігор, як Chess,Checkers and Ludoдо ConcreteElements.

У наведеному вище прикладі Chess, Checkers and Ludoтри різні ігри (та Visitableкласи). Одного прекрасного дня я зіткнувся зі сценарієм реєстрації статистики кожної гри. Таким чином, не змінюючи індивідуального класу на реалізацію функціональних даних статистики, ви можете централізувати цю відповідальність у GameVisitorкласі, що робить трюк для вас, не змінюючи структуру кожної гри.

вихід:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

Звертатися до

стаття oodesign

стаття про створення джерела

для отримання детальної інформації

Декоратор

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

Схожі повідомлення:

Шаблон декоратора для IO

Коли використовувати візерунок декоратора?


2

Мені дуже подобаються опис та приклад з http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.html .

Припущення полягає в тому, що у вас є ієрархія первинного класу, яка виправлена; можливо, це від іншого постачальника, і ви не можете внести зміни до цієї ієрархії. Однак ваш намір полягає в тому, що ви хочете додати нові поліморфні методи до цієї ієрархії, а це означає, що зазвичай вам доведеться щось додати до інтерфейсу базового класу. Отже, дилема полягає в тому, що вам потрібно додати методи до базового класу, але ви не можете торкатися базового класу. Як ти обійдеш це?

Шаблон дизайну, що вирішує подібну проблему, називається "відвідувачем" (остаточний у книзі "Шаблони дизайну"), і він будується на схемі подвійної диспетчеризації, показаній в останньому розділі.

Шаблон відвідувача дозволяє розширити інтерфейс основного типу, створивши окрему ієрархію класу Visitor для віртуалізації операцій, що виконуються над основним типом. Об'єкти основного типу просто “приймають” відвідувача, а потім викликають функцію учасника динамічно прив’язаного члена.


Хоча технічно шаблон відвідувачів, це справді просто основна подвійна відправка з їх прикладу. Я стверджую, що корисність особливо не видно лише з цього приводу.
Джордж Мауер

1

Поки я зрозумів, як і коли, я ніколи не зрозумів, чому. Якщо це допомагає комусь із фоном такою мовою, як C ++, ви хочете прочитати це дуже уважно.

Для ледачих ми використовуємо шаблон відвідувачів, оскільки "в той час, коли віртуальні функції динамічно розсилаються в C ++, перевантаження функцій відбувається статично" .

Або, по-іншому, переконатися, що CollideWith (ApolloSpacecraft &) викликається, коли ви передаєте посилання SpaceShip, яке фактично пов'язане з об'єктом ApolloSpacecraft.

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}

2
Використання динамічної диспетчеризації в схемі відвідувачів мене повністю вражає. Запропоновані схеми описують розгалуження, які можна зробити за час компіляції. Ці випадки, здавалося б, краще вирішити з шаблоном функції.
Праксеоліт

0

Дякую за дивовижне пояснення @Federico A. Ramponi , я щойно зробив це у версії Java . Сподіваюся, це може бути корисним.

Крім того, як зазначив @Konrad Rudolph , це фактично подвійна відправка, використовуючи два конкретні екземпляри разом для визначення методів виконання.

Тож насправді немає необхідності створювати загальний інтерфейс для виконавця операції , доки у нас правильно встановлений інтерфейс операції .

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

Як ви очікуєте, загальний інтерфейс принесе нам більше ясності, хоча це насправді не є істотною частиною цього шаблону.

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

0

Ваше питання, коли знати:

я не спочатку кодую з малюнком відвідувачів. Я кодую стандарт і чекаю, коли це станеться, а потім рефактор. тому скажемо, що у вас встановлено кілька платіжних систем, які ви встановлювали по одній. Під час оформлення каси у вас може бути багато, якщо умови (або instanceOf), наприклад:

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

тепер уявіть, у мене було 10 способів оплати, це стає якось некрасиво. Тож, коли ви бачите, що такий зразок, що виникає, відвідувач корисно відокремлює все, що виходить, і згодом ви називаєте щось подібне:

new PaymentCheckoutVistor(paymentType).visit()

Ви можете бачити, як це реалізувати, з кількості прикладів тут, я просто показую вам корисну скриньку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.