Visitor Pattern for Expression Trees: Adding Operations Without Modifying Existing Code, Papers of Computer Science

The visitor pattern is a design pattern that allows adding new operations to classes without changing them. In this document, we explore the use of the visitor pattern for expression trees, where we define two class hierarchies: one for the elements being operated on and one for the visitors that define operations on the elements. Each object structure, such as an expression tree, will have an associated visitor class, and the visitor will define visit operations for each concrete class. The visitor will traverse the structure and perform the desired operation on each element. The visitor pattern achieves this by using a technique called double-dispatch, where the operation used to fulfill a request is determined by the name of the request and the types of both the receiver and the visitor.

Typology: Papers

Pre 2010

Uploaded on 07/29/2009

koofers-user-2qe
koofers-user-2qe 🇺🇸

10 documents

1 / 31

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Software Design
CSE 335, Spring 2006
Visitors
Stephen Wagner
Michigan State University
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f

Partial preview of the text

Download Visitor Pattern for Expression Trees: Adding Operations Without Modifying Existing Code and more Papers Computer Science in PDF only on Docsity!

Software Design

CSE 335, Spring 2006

Visitors

Stephen Wagner

Michigan State University

Visitors

  • Suppose we have a complicated expression tree:
    • add, subtract, multiply
    • literals, variables, arrays
    • min, max, arbitrary functions
    • control logic
  • We can perform several operations
    • evaluate
    • type check
    • simplify

Visitors

  • With the visitor pattern you define two class hierarchies
    • one for the elements being operated on (e.g. the expression nodes)
    • one for the visitors that define operations on the elements
  • You create a new operation by adding a new class to the visitor class hierarchy
    • This will require writing lots of code
    • However we do not have to modify the expression node hierarchy

@@

@@

Expr

Literal Sum Product Func

ExprVisitor

EvaluateVisitor SimplifyVisitor

Operating Overloading

class ExprVisitor { public: virtual void Visit(Literal *); virtual void Visit(Sum *); virtual void Visit(Product *); virtual void Visit(Func *); // .... protected: Visitor(); }

  • Be careful!
  • All the Visit functions are different

Accept

  • Each concrete class implements an Accept operation that calls the matching Visit... operation on the visitor for that concrete class.
  • The operation that ends up getting called depends on both the class of the element and the class of the visitor

class Expr { class Literal : public Expr { public: public: virtual ~Expr(); Literal(Expr *, Expr *); virtual virtual void Accept(ExprVisitor &v) void Accept(ExprVisitor&)=0; { v.VisitLiteral(this); } protected: }; Expr(); };

Accept

class FuncExpr : public Expr { public: FuncExpr(); virtual void Accept(ExprVisitor &); private: list<Expr *> parameters; }

void FuncExpr::Accept (ExprVisitor & v) { for (list<Expr *>::iterator it=parameters.begin(); it!=parameters.end(); ++it) it->Accept(v); v.VisitFuncExpr(this); }

Double Dispatch

  • The Visitor pattern lets you add operations to classes without changing them
  • Visitor achieves this by using a technique called double-dispatch
  • single-dispatch:
    • the operation used to fulfill a request is determined by the name of the request and the type of the receiver
    • a->request();
    • C++ supports single-dispatch
  • double-dispatch:
    • the operation used to fulfill a request is determined by the name of the request and the type of two receivers
    • key to Visitor pattern

Evaluate Visitor

class EvaluateVisitor : public ExprVisitor { public: double GetValue();

virtual void VisitLiteral(Literal *); virtual void VisitSum(Sum *); virtual void VisitProduct(Product *); virtual void VisitMax(Max *);

private: stack values; };

void EvaluateVisitor::VisitLiteral(Literal *l) { values.push(l->value()); }

void EvaluateVisitor::VisitProduct(Product l) { double rightval, newval; rightval=values.top(); values.pop(); newval=values.top()rightval; values.pop(); values.push(newval); }

Simplify

class SimplifyVisitor : public ExprVisitor { public: bool check();

virtual void VisitLiteral(Literal *); virtual void VisitSum(Sum *); virtual void VisitProduct(Product *); virtual void VisitMax(Max *);

private: stack<Expr *> exprs; };

void SimplifyVisitor::VisitLiteral(Literal *l) { exprs.push(this->duplicate()); }

void SimplifyVisitor::VisitSum(Sum *l) { Expr *left=exprs.top(); exprs.pop(); Expr *right=exprs.top(); exprs.pop(); if (left->evaluate(leftvalue) && right->evaluate(rightvalue)) exprs.push_back(new Literal(leftvalue+rightvalue)); else if (left->evaluate(leftvalue) && leftvalue==0) exprs.push_back(right); else if (right->evaluate(rightvalue) && rightvalue==0) exprs.push_back(left); else exprs.push_back(new Sum(left,right)); }

Visitors

  • Visitors are not restricted to a class hierarchy

class Visitor { public: void VisitTypeOne(TypeOne *); void VisitTypeTwo(TypeTwo *); }

  • TypeOne and TypeTwo do not have to belong to the same hierarchy
  • Visitors can have state

Variations

  • Who is responsible for traversing the structure?
    • The components of the structure
    • The visitor
  • In previous examples, the nodes of the expression tree were responsible for traversing the tree
  • What are the advantages and disadvantages of the different methods?