Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
 
Рейтинг 5.00/6: Рейтинг темы: голосов - 6, средняя оценка - 5.00
1 / 1 / 0
Регистрация: 03.07.2016
Сообщений: 56
1

Изучаю паттерн Visitor

04.09.2019, 22:29. Показов 1158. Ответов 5
Метки нет (Все метки)

Не могу до конца понять смысл и реализацию. Для изучения дан вот такой пример...

Кликните здесь для просмотра всего текста

На каждую операцию над объектами иерархии мы могли бы заводить по отдельному виртуальному методу, но такой подход при большом количестве операций приведёт к значительному увеличению размера кода. Вместо этого мы могли бы определить какой-нибудь универсальный интерфейс, который позволял бы реализовывать операции, не раздувая интерфейс классов.

Напечатаем все бинарные операции, используемые в выражении с помощью наследника класса Visitor...

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <iostream>
using namespace std;
 
struct Number;
struct BinaryOperation;
 
struct Visitor {
    virtual void visitNumber(Number const * number) = 0;
    virtual void visitBinaryOperation(BinaryOperation const * binary) = 0;
    virtual ~Visitor() { }
};
 
struct Expression
{
    
    virtual double evaluate() const = 0;
    virtual ~Expression()
    {
        cout << "Expression delete!..." << endl;
    }
    virtual void visit(Visitor * visitor) const = 0;
};
 
struct Number : Expression
{
    Number(double value)
        : value(value)
    {}
 
    ~Number()
    {
        cout << "Number delete!..." << endl;
    }
 
    double evaluate() const
    {
        return value;
    }
 
    void visit(Visitor * visitor) const 
    { 
        visitor->visitNumber(this); 
    }
 
    double get_value() const 
    { 
        return value; 
    }
 
private:
    double value;
};
 
struct BinaryOperation : Expression
{
    /*
      Здесь op это один из 4 символов: '+', '-', '*' или '/', соответствующих операциям,
      которые вам нужно реализовать.
     */
    BinaryOperation(Expression const * left, char op, Expression * right)
        : left(left), op(op), right(right)
    {}
 
    ~BinaryOperation()
    {
        cout << "BinaryOperation delete!..." << endl;
        delete left;
        delete right;
    }
 
    double evaluate() const
    {
        switch (op)
        {
 
        case '+':
            return left->evaluate() + right->evaluate();
            break;
 
        case '-':
            return left->evaluate() - right->evaluate();
            break;
 
        case '*':
            return left->evaluate() * right->evaluate();
            break;
 
        case '/':
            return left->evaluate() / right->evaluate();
            break;
        }
    }
 
    void visit(Visitor * visitor) const 
    { 
        visitor->visitBinaryOperation(this); 
    }
 
    Expression const * get_left()  const 
    { 
        return left; 
    }
    
    Expression const * get_right() const 
    { 
        return right; 
    }
    
    char get_op() const 
    { 
        return op; 
    }
 
private:
    Expression const * left;
    Expression const * right;
    char op;
};
 
struct PrintBinaryOperationsVisitor : Visitor {
    void visitNumber(Number const * number)
    { }
 
    void visitBinaryOperation(BinaryOperation const * bop)
    {
        bop->get_left()->visit(this);
        std::cout << bop->get_op() << " ";
        bop->get_right()->visit(this);
    }
};
 
int main()
{
    setlocale(LC_ALL, "Russian");
 
    //Expression const * expr = get_expression();
    Expression * expr = new BinaryOperation(new Number(4.5), '*', new Number(5));
    
    PrintBinaryOperationsVisitor visitor;
    expr->visit(&visitor);
 
    return 0;
}


Код в целом кажется понятным, кроме вот этого...

C++
1
bop->get_left()->visit(this);
Сказано, что...

Данный код рекурсивно обойдёт дерево, соответствующее арифметическому выражению,
и напечатает все бинарные операции в порядке обхода.
Как это работает??

Нашел задание для самостоятельной работы, сделал, а проверить некому. Посмотрите пожалуйста правильно написал или нет...

Кликните здесь для просмотра всего текста

Реализовать иерарxию классов геометрическиx фигур и иерарxию классов Visiter,
с помощью которой будет выводиться площадь этиx фигур.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <iostream>
#include <math.h>
using namespace std;
 
struct Triangle;
struct Square;
struct Circle;
 
struct Visitor
{
    virtual void visitTriangle(Triangle *figure) = 0;
    virtual void visitSquare(Square *figure) = 0;
    virtual void visitCircle(Circle *figure) = 0;
};
 
struct Geometry
{
    virtual double get_figure_area() = 0;
    virtual void visit(Visitor *visitor) = 0;
};
 
struct Triangle : Geometry
{
    Triangle(double a, double b, double c)
        : a(a), b(b), c(c)
    {}
 
    double get_figure_area()
    {
        double p = (a + b + c) / 2;
        return sqrt(p*(p - a)*(p - b)*(p - c));
    }
 
    void visit(Visitor *visitor)
    {
        visitor->visitTriangle(this);
    }
 
    double a;
    double b;
    double c;
};
 
struct Square : Geometry
{
    Square(double a, double b)
        : a(a), b(b)
    {}
 
    double get_figure_area()
    {
        return a * b;
    }
 
    void visit(Visitor *visitor)
    {
        visitor->visitSquare(this);
    }
 
    double a;
    double b;
};
 
struct Circle : Geometry
{
    Circle(double r)
        : r(r)
    {}
 
    double get_figure_area()
    {
        return 3.14 * r * r;
    }
 
    void visit(Visitor *visitor)
    {
        visitor->visitCircle(this);
    }
 
    double r;
};
 
struct GetArea : Visitor
{
    void visitTriangle(Triangle *figure) 
    {
        cout << figure->get_figure_area() << endl;
    }
    
    void visitSquare(Square *figure)
    {
        cout << figure->get_figure_area() << endl;
    }
    
    void visitCircle(Circle *figure) 
    {
        cout << figure->get_figure_area() << endl;
    }
};
 
int main()
{
    setlocale(LC_ALL, "Russian");
 
    /*Реализовать иерарxию классов геометрическиx фигур и иерарxию классов Visiter, 
    с помощью которой будет выводиться площадь этиx фигур. То есть ваша функция 
    main будет выглядеть примерно так(напишите такой код, чтобы она выполнялась 
    и результатом вывода были бы площади соответствующиx фигур) :*/
 
        Geometry * geo;
        Triangle * tri = new Triangle(1, 1, 1);
        Square * sq = new Square(2, 2);
        Circle * cq = new Circle(3);
        geo = tri;
        GetArea viss;
        geo->visit(&viss);
        geo = sq;
        geo->visit(&viss);
        geo = cq;
        geo->visit(&viss);
    
    /*Geometry - базовый класс фигур.
    Triangle, Square, Circle - наследники Geometry.
    GetArea - наследник абстрактного класса Visit.*/
 
 
    return 0;
}


В заключении, может кто-нбудь поделится ссылкой на статью или видео с доступным объяснением!?
0

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
04.09.2019, 22:29
Ответы с готовыми решениями:

Паттерн Visitor для дерева
Есть у кого нибудь такой код? Буду благодарен!

Абстрактный класс Visitor С++
Пытаюсь создать абстрактный класс Visitor для работы с классом Expression, который представляет...

Паттерн Visitor
Для игры Реверс реализовать метод обхода доски перебирающий все эл-ты игрового поля и метод который...

Паттерн Visitor
Написал приложение для работы с бд. тема - кулинарная книга. 5 таблиц, можно добавить-удалить...

5
749 / 352 / 72
Регистрация: 10.06.2014
Сообщений: 2,371
04.09.2019, 23:32 2
Задание в целом сделано правильно.
Только в классах фигур метод visit желательно переименовать в accept.
Вот тут подробно расписан этот шаблон и многие другие
https://refactoring.guru/ru/de... ns/visitor

Если кратко, то вы просто выводите поведение которое так или иначе связано с фигурами в отдельный класс. Почему не описать это поведение напрямую в классе фигуры? Да потому что это не всегда может быть уместно. Этот отдельный класс и называется посетителем. Посетители посещают (visit) фигуры, а фигуры принимают посетителей (accept).

Фигура принимает экземпляр посетителя через метод accept и в этом методе обычно передаёт себя в посетитель (вызывает его метод visit и передаёт туда this). В итоге посетитель получает доступ к экземпляру фигуры, фактически к его this, и используя экземпляр фигуры внутри себя посетитель может дополнить логику фигуры, которую так не хочется размещать в самой фигуре напрямую.
2
Mental handicap
1245 / 623 / 171
Регистрация: 24.11.2015
Сообщений: 2,429
05.09.2019, 00:07 3
Цитата Сообщение от remzona Посмотреть сообщение
Реализовать иерарxию классов геометрическиx фигур и иерарxию классов Visiter,
с помощью которой будет выводиться площадь этиx фигур.
Если честно совсем не вижу постановки задачи для использования здесь данного паттерна.

В общем можно сказать, что паттерн Посетитель - это способ симуляции двойной диспетчеризации в C++, которая в свою очередь является способом моделирования (отсутствующих) мульти-методов в C++. Скажем Посетитель позволяет расширить доступный интерфейс без явного изменения самого объекта.
В добавок к юзкейсам паттерна: Допустим вы пользуетесь сторонней библиотекой и Вы как пользователь не можете изменять классы/интерфейсы в библиотеке. То есть, если вам нужно добавить новую операцию, единственный способ, которым Вы можете это сделать, - это воспользоваться данным паттерном. Если, конечно, такая возможность будет предвидена

Цитата Сообщение от remzona Посмотреть сообщение
В заключении, может кто-нбудь поделится ссылкой на статью или видео с доступным объяснением!?
Книжка "Банда четырех", называется.
3
Комп_Оратор)
Эксперт по математике/физике
8719 / 4425 / 598
Регистрация: 04.12.2011
Сообщений: 13,256
Записей в блоге: 16
05.09.2019, 10:54 4
Цитата Сообщение от remzona Посмотреть сообщение
Код в целом кажется понятным, кроме вот этого...
C++
1
2
3
4
5
6
void visitBinaryOperation(BinaryOperation const * bop)
    {
        bop->get_left()->visit(this);
        std::cout << bop->get_op() << " ";
        bop->get_right()->visit(this);
    }
Тут показан рекурсивный обход 2-дерева и это не имеет прямого отношения к паттерну visitor. Я не компилировал, но предвижу неприятности при попытке вызова get_left()/get_right() на указателе на базовый Expression оказавшийся листом - числом - Number ... Впрочем, наглаз могу и ошибиться.
Гланое, что данная строка не относится к теме visitor. remzona, патерн посетитель это специфический способ решения задачи в обход стандартных путей. Например, динамический полиморфизм, решает задачу элегантно и не нарушая инкапсуляции классов клиента. Вы хорошо понимаете задачу и способ решения при полиморфном наследовании?
1
1 / 1 / 0
Регистрация: 03.07.2016
Сообщений: 56
07.09.2019, 12:43  [ТС] 5
Достаточно хорошо.
0
Комп_Оратор)
Эксперт по математике/физике
8719 / 4425 / 598
Регистрация: 04.12.2011
Сообщений: 13,256
Записей в блоге: 16
07.09.2019, 13:04 6
Цитата Сообщение от remzona Посмотреть сообщение
Достаточно хорошо.
Цитата Сообщение от remzona Посмотреть сообщение
Не могу до конца понять смысл и реализацию.
remzona, ваш способ оценки достаточности нужно бы конкретизировать в более точных выражениях. Если вам кажется, что "не до конца" это то что нужно, чтобы понять что именно вызывает у вас трудности, то мне кажется, что это вам кажется.
Посетитель это выражение намерения не включать в класс часть его функционала и интерфейса в частности. Полиморфное наследование наглухо впечатывает в класс его методы. Плюсы и минусы очевидны.
Вообще, иерархии наследования предполагают фундаментальную изученность системы понятий выражаемых иерархией. В случае когда разработка опережает понимание происходящего (обычная сегодня ситуация) то приходится как-то расширять возможности в процессе жизненного цикла ПО. Речь идёт о кардинальных шагах. Посетитель - путь для такого расширения. Он предлагает индустриальную технологию нарушения инкапсуляции в пользу дружественных отношений. В результате, читаемость, отлаживаемость и сопровождение, не улучшаются.
remzona, объяснить патерн посетитель (он не самый сложный, но всё же...), с нуля не имеет смысла, поскольку в сети достаточно информации. Кроме того, это понятно лишь тогда, когда есть понимание плюсов и минусов решений задачи без использования данного подхода.
Задайте вопрос определённо.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
07.09.2019, 13:04

Помощь в написании контрольных, курсовых и дипломных работ здесь.

Почему паттерн абстрактная фабрика - паттерн уровня объектов, если в нём могут быть статические отношения?
Взято из Шевчук А., Охрименко Д., Касьянов А. Design Patterns via C#. Приемы...

По паттерну visitor
допустим, у меня есть Visitable с методом accept и Visitor с методом visit. Соответственно я пишу...

Returning Visitor (вовзраты) - 48% с AdWords - это нормально?
Всем доброго времени суток, с прошедшими и наступающими праздниками. Заинтересовал вот такой...

Если переменная visitor имеет значение "ВАСЯ", то...
Вот такое задание : greeting=(visitor==&quot;ВАСЯ&quot;)?&quot;Привет Вася &quot;:&quot;Привет&quot;; Если переменная...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
6
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2021, vBulletin Solutions, Inc.