Step 9 (S-42513)

From Stepik Wiki
Jump to: navigation, search

Step on Stepik: https://stepik.org/lesson/556/step/9

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

Для этого давайте заведем абстрактный класс Visitor:


struct Visitor {
    virtual void visitNumber(Number const * number) = 0;
    virtual void visitBinaryOperation(BinaryOperation const * binary) = 0;
    virtual ~Visitor() { }
};


И добавим в нашу иерархию методы visit:


struct Expression {
    /* ... */
    virtual void visit(Visitor * visitor) const  = 0;
    /* ... */
};

struct Number : Expression {
    /* ... */
    void visit(Visitor * visitor) const { visitor->visitNumber(this); }
    /* ... */
};


struct BinaryOperation : Expression {
    /* ... */
    void visit(Visitor * visitor) const { visitor->visitBinaryOperation(this); }
    /* ... */
};


Для того, чтобы Visitor мог сделать что-либо полезное, мы должны добавить в иерархию Expression методы доступа к свойствам классов (дабы объект класса Visitor мог к ним обратиться). Давайте определим метод для доступа к значению внутри объекта Number, а также методы для доступа к левому и правому операндам объекта BinaryOperation. Это будет выглядеть следующим образом: 

 

struct Number : Expression {
    /* ... */
    double get_value() const { return value; }
    /* ... */
};

struct BinaryOperation : Expression {
    /* ... */
    Expression const * get_left()  const { return left; }
    Expression const * get_right() const { return right; }
    char get_op() const { return op; }
    /* ... */
};


Теперь для того, чтобы определить какое-либо действие с арифметическим выражением, достаточно просто создать наследника класса Visitor и реализовать в нем методы visitNumber и visitBinaryOperation, которые реализуют требуемые действия. Например, напечатать все бинарные операции, используемые в выражении, можно с помощью следующего наследника класса Visitor:


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


После чего его можно использовать так:

Expression const * expr = get_expression();
PrintBinaryOperationsVisitor visitor;
expr->visit(&visitor);

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