Jump to content

Visitor pattern

From Rosetta Code
Visitor pattern is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Description

In object oriented programming, the Visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures.

It is one way to follow the open/closed principle which states that: "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".

The Visitor pattern is one of the twenty-three Gang of Four design patterns that facilitate the solution of recurring design problems in object-oriented software.

Operation

Consider two objects, each of some class type; one is termed the element, and the other is the visitor.

The visitor declares a visit method, which takes the element as an argument, for each class of element. Concrete visitors are derived from the visitor class and implement these visit methods, each of which implements part of the algorithm operating on the object structure. The state of the algorithm is maintained locally by the concrete visitor class.

The element declares an accept method to accept a visitor, taking the visitor as an argument. Concrete elements, derived from the element class, implement the accept method. Composite elements, which maintain a list of child objects, typically iterate over these, calling each child's accept method.

Having created the object structure, a program should first instantiate the concrete visitors. When an operation is to be performed which is implemented using the Visitor pattern, it should then call the accept method of the top-level element(s) passing the visitor(s) as arguments.

Examples

The Wikipedia article contains examples of the Visitor pattern written in: C#, Smalltalk, Go (partial), Java, Common Lisp and Python.

Task

Demonstrate the workings of the Visitor pattern in your language by translating one (or more) of the Wikipedia examples. If your language is one of those for which an example already exists, try to translate one of the other examples.

If you don't know any of the example languages or prefer to use your own example, then this is also acceptable.

If your language does not support the object oriented paradigm at all (or only to a limited extent), then try to emulate the intent of the pattern with the tools it does have by writing a program which produces the same output as one of the Wikipedia examples.

References


Ada

An Ada implementation of the Wikipedia Java example. Perhaps more packages than needed (7), which makes for quite a few files (specification + implementation). An overview:

  • Vehicle_Elements (spec + body) provides a base Element class for Car and its parts, as well as an Element_Interface for visitors. It's sufficiently abstract that you could, in principle, easily define a Bicycle, or a Truck, or an Airplane.
  • The elements of a Car are defined in:
  • Bodies
  • Engines
  • Wheels
  • Car_Visitors (spec + body) provides an implementation of Element_Visitor for Cars, defining two visitors:
  • Perform_Visitor
  • Print_Visitor
  • Cars (spec + body) "builds" a Car from its various parts and overrides Accept_Visitor.
  • Visitor_Pattern instantiates a car and invokes both visitors on it.

Vehicle_Elements

private with Ada.Strings.Unbounded;

package Vehicle_Elements is

   --  Forward declaration for visitor operation parameter
   type Element is tagged;

   --  Generic visitor interface
   type Element_Visitor is interface;

   --  Interface visiting procedure
   procedure Visit
     (Self :        Element_Visitor;
      Part : in out Vehicle_Elements.Element'Class) is abstract;

   --  Base class type for all car things
   type Element is abstract tagged private;

   --  Using 'Class here so I can provide a generic base class constructor
   --     Name - Name of the part: "Body", "Engine", "Wheel"
   --  NOTE:  When using to make an aggregate, type convert the result of this
   --     operation to the Element type
   function Make (Name : String) return Element'Class;

   --  To get the supplied name
   function Name (Self : Element'Class) return String;

   --  This procedure calls Visitor.Visit(Self) by default
   --  We can't call it `Accept` because `accept` is an Ada keyword...
   procedure Accept_Visitor
     (Self : in out Element; Visitor : Element_Visitor'Class);

private

   use Ada.Strings.Unbounded;

   type Element is abstract tagged record
      Name : Unbounded_String;
   end record;

end Vehicle_Elements;
package body Vehicle_Elements is

   --  Need a non abstract type to actually work in the Make function
   type Factory is new Element with null record;

   function Make (Name : String) return Element'Class is
     (Factory'(Name => To_Unbounded_String (Name)));

   function Name (Self : Element'Class) return String is
     (To_String (Self.Name));

   procedure Accept_Visitor
     (Self : in out Element; Visitor : Element_Visitor'Class)
   is
   begin
      Visitor.Visit (Self);
   end Accept_Visitor;

end Vehicle_Elements;

Car_Visitors

with Vehicle_Elements;

package Car_Visitors is

   type Print_Visitor is new Vehicle_Elements.Element_Visitor with null record;
   overriding procedure Visit
     (Self : Print_Visitor; Part : in out Vehicle_Elements.Element'Class);

   type Perform_Visitor is
   new Vehicle_Elements.Element_Visitor with null record;
   overriding procedure Visit
     (Self : Perform_Visitor; Part : in out Vehicle_Elements.Element'Class);

end Car_Visitors;
with Ada.Text_IO; use Ada.Text_IO;
with Bodies;
with Engines;
with Wheels;
with Cars;

package body Car_Visitors is

   overriding procedure Visit
     (Self : Print_Visitor; Part : in out Vehicle_Elements.Element'Class)
   is
   begin
      Put_Line ("Visiting " & Part.Name);
   end Visit;

   overriding procedure Visit
     (Self : Perform_Visitor; Part : in out Vehicle_Elements.Element'Class)
   is
   begin
      if Part in Cars.Car then
         Put_Line ("Starting the " & Part.Name);
      elsif Part in Bodies.Car_Body then
         Put_Line ("Moving the " & Part.Name);
      elsif Part in Engines.Engine then
         Put_Line ("Revving the " & Part.Name);
      elsif Part in Wheels.Wheel then
         Put_Line ("Rolling the " & Part.Name);
      else
         raise Constraint_Error
           with "Peform_Visitor does not support part type";
      end if;
   end Visit;

end Car_Visitors;

The parts of a Car

with Vehicle_Elements;

package Bodies is

   type Car_Body is new Vehicle_Elements.Element with null record;
   function Make return Car_Body is
     (Vehicle_Elements.Element (Vehicle_Elements.Make ("Body")) with
      null record);

end Bodies;
with Vehicle_Elements;

package Engines is

   type Engine is new Vehicle_Elements.Element with null record;
   function Make return Engine is
     (Vehicle_Elements.Element (Vehicle_Elements.Make ("Engine")) with
      null record);

end Engines;
with Vehicle_Elements;

package Wheels is

   type Wheel is new Vehicle_Elements.Element with null record;
   function Make (Name : String) return Wheel is
     (Vehicle_Elements.Element (Vehicle_Elements.Make (Name & " Wheel")) with
      null record);

end Wheels;

Cars

private with Wheels;

with Bodies;
with Engines;
with Vehicle_Elements;

package Cars is

   type Car is new Vehicle_Elements.Element with private;

   overriding procedure Accept_Visitor
     (Self : in out Car; Visitor : Vehicle_Elements.Element_Visitor'Class);

   function Make return Car;

private

   type Wheel_Position is (Left_Front, Right_Front, Left_Back, Right_Back);

   type Wheel_Array is array (Wheel_Position) of Wheels.Wheel;

   type Car is new Vehicle_Elements.Element with record
      Car_Body   : Bodies.Car_Body;
      Engine     : Engines.Engine;
      All_Wheels : Wheel_Array;
   end record;

end Cars;
pragma Ada_2022;

package body Cars is

   function Make return Car is
     (Vehicle_Elements.Element (Vehicle_Elements.Make ("Car")) with
      Car_Body   => Bodies.Make, Engine => Engines.Make,
      All_Wheels =>
        (for Wheel in Wheel_Position => Wheels.Make (Wheel'Image)));

   overriding procedure Accept_Visitor
     (Self : in out Car; Visitor : Vehicle_Elements.Element_Visitor'Class)
   is
   begin
      Vehicle_Elements.Element (Self).Accept_Visitor (Visitor);
      Self.Car_Body.Accept_Visitor (Visitor);
      Self.Engine.Accept_Visitor (Visitor);
      for Wheel of Self.All_Wheels loop
         Wheel.Accept_Visitor (Visitor);
      end loop;
   end Accept_Visitor;

end Cars;

Putting it all together

with Cars;
with Car_Visitors;

procedure Visitor_Pattern is
   Car       : Cars.Car := Cars.Make;
   Printer   : Car_Visitors.Print_Visitor;
   Performer : Car_Visitors.Perform_Visitor;
begin
   Car.Accept_Visitor (Printer);
   Car.Accept_Visitor (Performer);
end Visitor_Pattern;

FreeBASIC

Translation of C# example

' Interface Visitor (implemented through function pointers)
Type Visitor
    visitaLiteral As Sub(Byval literal As Any Ptr)
    visitaAdicion As Sub(Byval addition As Any Ptr)
End Type

' Forward declarations
Type Expression
    aceptar As Sub(Byval This As Expression Ptr, Byval v As Visitor Ptr)
    obtenerValor As Function(Byval This As Expression Ptr) As Double
End Type

Type Literal
    bbase As Expression
    valor As Double
End Type

Type Addition 
    bbase As Expression
    izda As Expression Ptr
    dcha As Expression Ptr
End Type

' Expression Printing Visitor implementation
Sub visitaLiteral(Byval literal As Any Ptr)
    Dim lit As Literal Ptr = literal
    Print lit->valor
End Sub

Sub visitaAdicion(Byval addition As Any Ptr)
    Dim anade As Addition Ptr = addition
    Dim leftValue As Double = anade->izda->obtenerValor(anade->izda)
    Dim rightValue As Double = anade->dcha->obtenerValor(anade->dcha)
    Dim sum As Double = leftValue + rightValue
    Print Using " # + # = #"; leftValue; rightValue; sum
End Sub

' Literal methods
Sub aceptarLiteral(Byval This As Expression Ptr, Byval v As Visitor Ptr)
    v->visitaLiteral(This)
End Sub

Function obtenerValorLiteral(Byval This As Expression Ptr) As Double
    Dim lit As Literal Ptr = Cptr(Literal Ptr, This)
    Return lit->valor
End Function

Function crearLiteral(Byval value As Double) As Literal Ptr
    Dim lit As Literal Ptr = New Literal
    lit->bbase.aceptar = @aceptarLiteral
    lit->bbase.obtenerValor = @obtenerValorLiteral
    lit->valor = value
    Return lit
End Function

' Addition methods
Sub adicionAceptar(Byval This As Expression Ptr, Byval v As Visitor Ptr)
    Dim anade As Addition Ptr = Cptr(Addition Ptr, This)
    anade->izda->aceptar(anade->izda, v)
    anade->dcha->aceptar(anade->dcha, v)
    v->visitaAdicion(This)
End Sub

Function adicionObtenerValor(Byval This As Expression Ptr) As Double
    Dim anade As Addition Ptr = Cptr(Addition Ptr, This)
    Return anade->izda->obtenerValor(anade->izda) + anade->dcha->obtenerValor(anade->dcha)
End Function

Function adicionCrear(Byval izda As Expression Ptr, Byval dcha As Expression Ptr) As Addition Ptr
    Dim anade As Addition Ptr = New Addition
    anade->bbase.aceptar = @adicionAceptar
    anade->bbase.obtenerValor = @adicionObtenerValor
    anade->izda = izda
    anade->dcha = dcha
    Return anade
End Function

' Main program
Sub main()
    ' Create visitor
    Dim printingVisitor As Visitor
    printingVisitor.visitaLiteral = @visitaLiteral
    printingVisitor.visitaAdicion = @visitaAdicion
    
    ' Create expression tree: 1 + 2 + 3
    Dim lit1 As Literal Ptr = crearLiteral(1)
    Dim lit2 As Literal Ptr = crearLiteral(2)
    Dim lit3 As Literal Ptr = crearLiteral(3)
    
    Dim add1 As Addition Ptr = adicionCrear(@lit1->bbase, @lit2->bbase)
    Dim add2 As Addition Ptr = adicionCrear(@add1->bbase, @lit3->bbase)
    
    ' Accept visitor
    add2->bbase.aceptar(@add2->bbase, @printingVisitor)
    
    ' Cleanup
    Delete lit1
    Delete lit2
    Delete lit3
    Delete add1
    Delete add2
End Sub

main()

Sleep

Translation of Java example

' Forward declarations
Type CarElementVisitor As CarElementVisitor_
Type CarElement As CarElement_
Type Wheel As Wheel_
Type Body As Body_
Type Engine As Engine_
Type Car As Car_

' Visitor interface
Type CarElementVisitor_
    visit_wheel As Sub(Byval This As CarElementVisitor Ptr, Byval wheel As Wheel Ptr)
    visit_body As Sub(Byval This As CarElementVisitor Ptr, Byval body As Body Ptr)
    visit_engine As Sub(Byval This As CarElementVisitor Ptr, Byval engine As Engine Ptr)
    visit_car As Sub(Byval This As CarElementVisitor Ptr, Byval car As Car Ptr)
End Type

' Base element interface
Type CarElement_
    accept As Sub(Byval This As CarElement Ptr, Byval visitor As CarElementVisitor Ptr)
End Type

' Wheel implementation
Type Wheel_
    As CarElement bbase
    nombre As String * 32
End Type

Sub wheel_accept(Byval This As CarElement Ptr, Byval visitor As CarElementVisitor Ptr)
    visitor->visit_wheel(visitor, Cptr(Wheel Ptr, This))
End Sub

Function create_wheel(Byval nombre As String) As Wheel Ptr
    Dim As Wheel Ptr wheel = New Wheel
    wheel->bbase.accept = @wheel_accept
    wheel->nombre = nombre
    Return wheel
End Function

' Body implementation
Type Body_
    As CarElement bbase
End Type

Sub body_accept(Byval This As CarElement Ptr, Byval visitor As CarElementVisitor Ptr)
    visitor->visit_body(visitor, Cptr(Body Ptr, This))
End Sub

Function create_body() As Body Ptr
    Dim As Body Ptr body = New Body
    body->bbase.accept = @body_accept
    Return body
End Function

' Engine implementation
Type Engine_
    As CarElement bbase
End Type

Sub engine_accept(Byval This As CarElement Ptr, Byval visitor As CarElementVisitor Ptr)
    visitor->visit_engine(visitor, Cptr(Engine Ptr, This))
End Sub

Function create_engine() As Engine Ptr
    Dim As Engine Ptr engine = New Engine
    engine->bbase.accept = @engine_accept
    Return engine
End Function

' Car implementation
Type Car_
    As CarElement bbase
    elements(5) As CarElement Ptr
End Type

Sub car_accept(Byval This As CarElement Ptr, Byval visitor As CarElementVisitor Ptr)
    Dim As Car Ptr car = Cptr(Car Ptr, This)
    For i As Integer = 0 To 5
        car->elements(i)->accept(car->elements(i), visitor)
    Next
    visitor->visit_car(visitor, car)
End Sub

Function create_car() As Car Ptr
    Dim As Car Ptr car = New Car
    car->bbase.accept = @car_accept
    car->elements(0) = Cptr(CarElement Ptr, create_wheel("front left"))
    car->elements(1) = Cptr(CarElement Ptr, create_wheel("front right"))
    car->elements(2) = Cptr(CarElement Ptr, create_wheel("back left"))
    car->elements(3) = Cptr(CarElement Ptr, create_wheel("back right"))
    car->elements(4) = Cptr(CarElement Ptr, create_body())
    car->elements(5) = Cptr(CarElement Ptr, create_engine())
    Return car
End Function

' Print Visitor implementation
Type CarElementPrintVisitor
    As CarElementVisitor bbase
End Type

Sub print_visit_wheel(Byval This As CarElementVisitor Ptr, Byval wheel As Wheel Ptr)
    Print "Visiting " & Rtrim(wheel->nombre) & " wheel"
End Sub

Sub print_visit_body(Byval This As CarElementVisitor Ptr, Byval body As Body Ptr)
    Print "Visiting body"
End Sub

Sub print_visit_engine(Byval This As CarElementVisitor Ptr, Byval engine As Engine Ptr)
    Print "Visiting engine"
End Sub

Sub print_visit_car(Byval This As CarElementVisitor Ptr, Byval car As Car Ptr)
    Print "Visiting car"
End Sub

Function create_print_visitor() As CarElementVisitor Ptr
    Dim As CarElementPrintVisitor Ptr visitor = New CarElementPrintVisitor
    visitor->bbase.visit_wheel = @print_visit_wheel
    visitor->bbase.visit_body = @print_visit_body
    visitor->bbase.visit_engine = @print_visit_engine
    visitor->bbase.visit_car = @print_visit_car
    Return Cptr(CarElementVisitor Ptr, visitor)
End Function

' Do Visitor implementation
Type CarElementDoVisitor
    As CarElementVisitor bbase
End Type

Sub do_visit_wheel(Byval This As CarElementVisitor Ptr, Byval wheel As Wheel Ptr)
    Print "Kicking my " & Rtrim(wheel->nombre) & " wheel"
End Sub

Sub do_visit_body(Byval This As CarElementVisitor Ptr, Byval body As Body Ptr)
    Print "Moving my body"
End Sub

Sub do_visit_engine(Byval This As CarElementVisitor Ptr, Byval engine As Engine Ptr)
    Print "Starting my engine"
End Sub

Sub do_visit_car(Byval This As CarElementVisitor Ptr, Byval car As Car Ptr)
    Print "Starting my car"
End Sub

Function create_do_visitor() As CarElementVisitor Ptr
    Dim As CarElementDoVisitor Ptr visitor = New CarElementDoVisitor
    visitor->bbase.visit_wheel = @do_visit_wheel
    visitor->bbase.visit_body = @do_visit_body
    visitor->bbase.visit_engine = @do_visit_engine
    visitor->bbase.visit_car = @do_visit_car
    Return Cptr(CarElementVisitor Ptr, visitor)
End Function

' Main program
Sub main()
    Dim As Car Ptr car = create_car()
    Dim As CarElementVisitor Ptr print_visitor = create_print_visitor()
    Dim As CarElementVisitor Ptr do_visitor = create_do_visitor()
    
    car->bbase.accept(@car->bbase, print_visitor)
    Print
    car->bbase.accept(@car->bbase, do_visitor)
    
    ' Cleanup
    Delete car
    Delete print_visitor
    Delete do_visitor    
End Sub

main()

Sleep

Julia

abstract type CarElementVisitor end

struct CarElementDoVisitor <: CarElementVisitor end
struct CarElementPrintVisitor <: CarElementVisitor end

abstract type CarElement end
struct Body <: CarElement end
struct Engine <: CarElement end

struct Wheel <: CarElement
    name::String
    Wheel(str::String) = new(str)
end

struct Car <:CarElement
    elements::Vector{CarElement}
    Car() = new([Wheel("front left"), Wheel("front right"), 
                 Wheel("rear left"), Wheel("rear right"),
                 Body(), Engine()])
end

accept(e::CarElement, visitor::CarElementVisitor) = visit(visitor, e)

function accept(car::Car, visitor::CarElementVisitor)
    for element in car.elements
        accept(element, visitor)
    end
    visit(visitor, car)
end

visit(v::CarElementDoVisitor, e::Body) = println("Moving my body.")
visit(v::CarElementDoVisitor, e::Car) = println("Starting my car.")
visit(v::CarElementDoVisitor, e::Wheel) = println("Kicking my $(e.name) wheel.")
visit(v::CarElementDoVisitor, e::Engine) = println("Starting my engine.")

visit(v::CarElementPrintVisitor, e::Body) = println("Visiting body.")
visit(v::CarElementPrintVisitor, e::Car) = println("Visiting car.")
visit(v::CarElementPrintVisitor, e::Wheel) = println("Visiting $(e.name) wheel.")
visit(v::CarElementPrintVisitor, e::Engine) = println("Visiting engine.")

car = Car()
accept(car, CarElementPrintVisitor())
println()
accept(car, CarElementDoVisitor())
Output:

Same as Phix entry.

M2000 Interpreter

CustomerGroup is an object with no pointer (has a hidden one) and erased at the exit of the module where defined it (the module not shown here, supposed there is a Module A { code here } : A ' to call the module)

All other objects are pointers to objects. We can place in class item the Remove {Print "remove one ite"} as a module which called at deconstruction, when no more pointers point to object, to see when the item removed.

The This identifier is the pointer if the object is pointer based, or the copy of object if it is like a variable (like the CustomerGroup). We can make the CustomerGroup a pointer using CustomerGroup->CustomerGroup() and we have to change all dots with =>, e.gCustomerGroup=>addCustomer Pointer((M1)), and the call to accept CustomerGroup=>accept GeneralReport (is faster for interpreter to use thn non pointer version).

The For This {} structure execute a part of code where new identifiers erased at the exit of it (used for temporary definitions). Here Order1 and M1 are non pointer objects. Although we make pointer((Order1)) as a pointer to a copy, so after the exit of For This { } block the pointers hold the copies of non poiner objects. In M2000 there are 3 stages for objects: A named Group type (like a variable), a float Group type, nameless (in a container like an array or somewhere else) which deleted when the container deleted, and the pointed Groups, where the pointer written in plain variables or containers. There are two type of pointers, the real one and the reference one (a reference to a non pointed Group). So we can pass in a module or function, which ask for a pointer to a specific type of object (of general Group type), either a real pointer or a fake one (the reference type). We get error if we store the reference one and the actual referenced object not exist at the moment we use it (and the "soft" reference will became a real one).

There is no abstract type but we can make it using Error statement where we have to define a non abstract member for named code like a module or function. So Class Visitor is abstract for the private members but not for the public one. If we use it as is we get error. So we have Class GeneralReport as Visitor, which replace the private modules with real (non abstract) modules. Interpreter make new obect merging class Visitor with class GeneralReport (classes are global functions which return objects), and at the merging all the modules/functions which are not Final can get new definitions. So GeneralReport is type GeneralReport and is type Visitor (because these two types merged). So visitable type handle Visitor type. We have Item, Order and Customer as visitable, so from these we can call any visitor object, through accept member (module accept) which we have to defined them to replace the Error "Abstract" code of the Class Visitable.


Class Visitor {
Private:
      module VisitItem (p as Item) {
            Error "Abstract"
      }
      module VisitOrder (p as Order) {
            Error "Abstract"
      }
      module VisitCustomer (p as Customer) {
            Error "Abstract"
      }
Public:
      module Visit (p as *Visitable){
            if p is type Item then
                  .VisitItem p
            else.if p is type Order then
                  .VisitOrder p
            else.if p is type Customer then
                  .VisitCustomer p
            end if
      }
}
Class Visitable {
      module accept (visitor as *Visitor){
            Error "Abstract"
      }
}
\\ a structure of objects
Class Item as Visitable {
      property name$ {Value}
      module accept (visitor as *Visitor){
            \\ do nothing
      }
Class:
      module Item (.[name]$) {
      }
}
Class Order as Visitable {
Private:
      Items=Queue
Public:
      property name$ {Value}
      module final accept (visitor as *Visitor){
            m=each(.Items)
            while m
                  visitor=>visit eval(m)
            end while
      }
      module addItem (aName$){
            Append .items, aName$:=Pointer(item(aName$))
      }
Class:
      module Order (.[name]$, itemname$="") {
            if itemname$<>"" then .additem itemname$
      }
}
Class Customer as Visitable {
Private:
      orders=List
Public:
      property name$ {Value}
      module final accept (visitor as *Visitor){
            m=each(.orders)
            while m
                  visitor=>visit eval(m)
            end while
      }
      module addOrder (order as *Order) {
            Append .orders, order=>name$:=order
      }
      module Customer (.[name]$) {
      }
}
Class CustomerGroup as Visitable {
Private:
      customers=List
Public:
      module final accept (visitor as *Visitor){
            m=each(.customers)
            while m
                  visitor=>visit eval(m)
            end while
      }
      module addCustomer (customer as *Customer) {
            Append .customers, customer=>name$:=customer
      }
}
Class GeneralReport as Visitor {
Private:
      ItemNo, OrderNo, CustomerNo
      Document Result$
      module VisitItem (p as *Item) {
            .ItemNo++
            .Result$<=format$("{0::-6}. {1}",.ItemNo, p=>name$)+{
            }
      }
      module VisitOrder (p as *Order) {
            .OrderNo++
            .ItemNo<=0
            .Result$<=format$("Order : {0}",p=>name$)+{
            }
            p=>accept pointer(this)
      }
      module VisitCustomer (p as *Customer) {
            .CustomerNo++
            .OrderNo<=0
            .Result$<=format$("Customer: {0}", p=>name$)+{
            }            
            p=>accept pointer(this)
      }
Public:
      module displayResults {
            Report .Result$
            ClipBoard .Result$
            Clear .Result$
            .CustomerNo<=0
      }
}
CustomerGroup=CustomerGroup()
\\ put some items in an order
For This {
      order1=Order("1001", "AZX100")
      order1.addItem "ZZ12-23"
      M1=Customer("Bob")
      M1.addOrder Pointer((order1))
      order1=Order("1002", "AZX101")
      M1.addOrder Pointer((order1))
      CustomerGroup.addCustomer Pointer((M1))
      order1=Order("1003", "KKX112")
      order1.addItem "BZ212-6"
      order1.addItem "BZ212-7"
      M1=Customer("John")
      M1.addOrder Pointer((order1))
      CustomerGroup.addCustomer Pointer((M1))
}
\\ Now order1 and M1 erased
\\ we get a pointer to a GeneralReport
GeneralReport->GeneralReport()
CustomerGroup.accept GeneralReport
GeneralReport=>displayResults
Output:
Customer: Bob
Order : 1001
     1. AZX100
     2. ZZ12-23
Order : 1002
     1. AZX101
Customer: John
Order : 1003
     1. KKX112
     2. BZ212-6
     3. BZ212-7

Nim

This is a translation of the Wikipedia C# example.

Note that Nim has no notion of “class” but only object types which allow simple inheritance. But it provides a way to define methods with dynamic dispatch and allows procedure overloading. So the translation of the C# example is easy.

import std/strutils

type

  ExpressionPrintingVisitor = object

  Expression = ref object of RootObj

  Literal = ref object of Expression
    value: float

  Addition = ref object of Expression
    left, right: Expression


# Expression procedures and methods.

method accept(e: Expression; v: ExpressionPrintingVisitor) {.base.} =
  raise newException(CatchableError, "Method without implementation override")

method getValue(e: Expression): float {.base.} =
  raise newException(CatchableError, "Method without implementation override")


# ExpressionPrintingVisitor procedures.

proc printLiteral(v: ExpressionPrintingVisitor; literal: Literal) =
  echo literal.value

proc printAddition(v: ExpressionPrintingVisitor; addition: Addition) =
  let leftValue = addition.left.getValue()
  let rightValue = addition.right.getValue()
  let sum = addition.getValue()
  echo "$1 + $2 = $3".format(leftValue, rightValue, sum)


# Literal procedure and methods.
proc newLiteral(value: float): Literal =
  Literal(value: value)

method accept(lit: Literal; v: ExpressionPrintingVisitor) =
  v.printLiteral(lit)

method getValue(lit: Literal): float = lit.value


# Addition procedure and methods.
proc newAddition(left, right: Expression): Addition =
  Addition(left: left, right: right)

method accept(a: Addition; v: ExpressionPrintingVisitor) =
  a.left.accept(v)
  a.right.accept(v)
  v.printAddition(a)

method getValue(a: Addition): float =
  a.left.getValue() + a.right.getValue()


proc main() =
  # Emulate 1 + 2 + 3.
  let e = newAddition(
            newAddition(newLiteral(1), newLiteral(2)),
            newLiteral(3))
  var printingVisitor: ExpressionPrintingVisitor
  e.accept(printingVisitor)

main()
Output:
1.0
2.0
1.0 + 2.0 = 3.0
3.0
3.0 + 3.0 = 6.0

Phix

Quote of the day: Object oriented programs are offered as alternatives to correct ones... - Edsger Dijkstra
Completely beyond me why anyone would actually want(/need) this sort of nonsense, but there's nothing at all difficult here.

without javascript_semantics

abstract class CarElement
    public string name
    procedure Accept()
        throw("Derived classes *MUST* implement this")
    end procedure
end class

abstract class Visitable
    procedure Visit(CarElement e)
        throw("Derived classes *MUST* implement this")
    end procedure
end class

class CarPart extends CarElement
    procedure Accept(Visitable visitor)
        visitor.Visit(this)
    end procedure
end class

class Body extends CarPart
end class

class Engine extends CarPart
end class

class Wheel extends CarPart
    function Wheel(string name)
        this.name = name & " wheel"
        return this
    end function
end class

class Car extends CarPart
    sequence elements   
    function Car(string name)
        this.name = name
        elements = {new(Wheel,{"front left"}),
                    new(Wheel,{"front right"}),
                    new(Wheel,{"back left"}),
                    new(Wheel,{"back right"}),
                    new(Body,{"body"}),
                    new(Engine,{"engine"})}
        return this
    end function
    procedure Accept(Visitable visitor)
        CarElement element
        for element in elements do
            element.Accept(visitor)
        end for
        visitor.Visit(this)
    end procedure
end class

class CarElementPrintVisitor extends Visitable
    procedure Visit(CarElement e)
        printf(1,"Visiting %s.\n",{e.name})
    end procedure
end class

class CarElementDoVisitor extends Visitable
    procedure Visit(CarElement e)
        string verb
        if Body(e) then
            verb = "Moving"
        elsif Car(e) or Engine(e) then
            verb = "Starting"
        elsif Wheel(e) then
            verb = "Kicking"
        end if
        printf(1,"%s my %s.\n",{verb,e.name})
    end procedure
end class

Car car = new({"car"})
car.Accept(new(CarElementPrintVisitor))
car.Accept(new(CarElementDoVisitor))
Output:
Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.

Python

This is based on the Wikipedia Python example, but uses structural pattern matching instead of multiple visit methods.

"""An example of the visitor pattern using structural pattern matching.

Requires Python >= 3.10.
"""
from __future__ import annotations

from abc import ABC
from abc import abstractmethod


class CarElement(ABC):
    def accept(self, visitor: CarElementVisitor) -> None:
        visitor.visit(self)


class CarElementVisitor(ABC):
    @abstractmethod
    def visit(self, car_element: CarElement) -> None:
        """Override this in `CarElementVisitor` subclasses."""


class Body(CarElement):
    """Car body."""


class Engine(CarElement):
    """Car engine."""


class Wheel(CarElement):
    """Car wheel"""

    def __init__(self, name: str) -> None:
        self.name = name


class Car(CarElement):
    def __init__(self) -> None:
        self.elements: list[CarElement] = [
            Wheel("front left"),
            Wheel("front right"),
            Wheel("back left"),
            Wheel("back right"),
            Body(),
            Engine(),
        ]

    def accept(self, visitor: CarElementVisitor) -> None:
        for element in self.elements:
            visitor.visit(element)
        super().accept(visitor)


class CarElementDoVisitor(CarElementVisitor):
    def visit(self, car_element: CarElement) -> None:
        match car_element:
            case Body():
                print("Moving my body.")
            case Car():
                print("Starting my car.")
            case Wheel() as wheel:
                print(f"Kicking my {wheel.name} wheel.")
            case Engine():
                print("Starting my engine.")


class CarElementPrintVisitor(CarElementVisitor):
    def visit(self, car_element: CarElement) -> None:
        match car_element:
            case Body():
                print("Visiting body.")
            case Car():
                print("Visiting car.")
            case Wheel() as wheel:
                print(f"Visiting my {wheel.name} wheel.")
            case Engine():
                print("Visiting my engine.")


if __name__ == "__main__":
    car = Car()
    car.accept(CarElementPrintVisitor())
    car.accept(CarElementDoVisitor())
Output:
Visiting my front left wheel.
Visiting my front right wheel.
Visiting my back left wheel.
Visiting my back right wheel.
Visiting body.
Visiting my engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.

Raku

Raku implements multiple dispatch so the visitor pattern is perhaps not as useful/necessary there. That said, it can be done fairly easily.

(Largely based on an example published by Johnathan Stowe.)

role CarElementVisitor { ... }

class CarElement {
    method accept(CarElementVisitor $visitor) {
        $visitor.visit: self
    }
}

class Body is CarElement { }

class Engine is CarElement { }

class Wheel is CarElement {
    has Str $.name is required;
}

class Car is CarElement {
    has CarElement @.elements = ( 
        Wheel.new(name => "front left"),
        Wheel.new(name => "front right"),
        Wheel.new(name => "rear left"),
        Wheel.new(name => "rear right"),
        Body.new,
        Engine.new
    );

    method accept(CarElementVisitor $visitor) {
        for @.elements -> $element { $element.accept: $visitor };
        $visitor.visit: self;
    }
}

role CarElementVisitor {
    method visit(CarElement $e) { ... }
}

class CarElementDoVisitor does CarElementVisitor {
    multi method visit(Body $e) {
        say "Moving my body.";
    }
    multi method visit(Car $e) {
        say "Starting my car.";
    }
    multi method visit(Wheel $e) {
        say "Kicking my { $e.name } wheel.";
    }
    multi method visit(Engine $e) {
        say "Starting my engine.";
    }
}

class CarElementPrintVisitor does CarElementVisitor {
    multi method visit(Body $e) {
        say "Visiting body.";
    }
    multi method visit(Car $e) {
        say "Visiting car.";
    }
    multi method visit(Wheel $e) {
        say "Visiting { $e.name } wheel.";
    }
    multi method visit(Engine $e) {
        say "Visiting engine.";
    }
}

my Car $car = Car.new;

$car.accept: CarElementPrintVisitor.new;
$car.accept: CarElementDoVisitor.new;
Output:
Visiting front left wheel.
Visiting front right wheel.
Visiting rear left wheel.
Visiting rear right wheel.
Visiting body.
Visiting engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my rear left wheel.
Kicking my rear right wheel.
Moving my body.
Starting my engine.
Starting my car.

Wren

Translation of C# example

As is often the case in practice, the following example departs somewhat from the typical operation of the pattern described above. There is no abstract Visitor class - only a concrete Visitor class - and the 'visit' methods are called something else.

class ExpressionPrintingVisitor {
    construct new(){}

    printLiteral(literal) { System.print(literal.value) }

    printAddition(addition) {
        var leftValue   = addition.left.value
        var rightValue  = addition.right.value
        var sum = addition.value
        System.print("%(leftValue) + %(rightValue) = %(sum)")
    }
}

// abstract class
class Expression {
    accept(visitor) {}
    value {}
}

class Literal is Expression {
    construct new(value) {
        _value = value
    }

    value       { _value }
    value=(val) { _value = val }

    accept(visitor) {
        visitor.printLiteral(this)
    }
}

class Addition is Expression {
    construct new(left, right) {
        _left = left
        _right = right
    }

    left        { _left }
    left=(exp)  { _left = exp }

    right       { _right }
    right=(exp) { _right = exp }

    accept(visitor) {
        _left.accept(visitor)
        _right.accept(visitor)
        visitor.printAddition(this)
    }

    value { _left.value + _right.value }
}

// Emulate 1 + 2 + 3
var e = Addition.new(
    Addition.new(Literal.new(1), Literal.new(2)),
    Literal.new(3)
)
var printingVisitor = ExpressionPrintingVisitor.new()
e.accept(printingVisitor)
Output:
1
2
1 + 2 = 3
3
3 + 3 = 6

Translation of Java example

Library: Wren-str

Note that Wren is dynamically typed and can only overload methods based on arity and not on argument type. In the following example, rather than having separate methods for each element type, we instead have a single 'visit' method which tests the type of the argument at run time and takes the appropriate action.

import "./str" for Str

// abstract class
class CarElement {
    accept(visitor) {}
}

// abstract class
class CarElementVisitor {
    visit(obj) {}
}

class Wheel is CarElement {
    construct new(name) {
        _name = name
    }

    name { _name }

    accept(visitor) {
        visitor.visit(this)
    }
}

class Body is CarElement {
    construct new() {}

    accept(visitor) {
        visitor.visit(this)
    }
}

class Engine is CarElement {
    construct new() {}

    accept(visitor) {
        visitor.visit(this)
    }
}

class Car is CarElement {
    construct new() {
        _elements = [
            Wheel.new("front left"), Wheel.new("front right"),
            Wheel.new("back left"), Wheel.new("back right"),
            Body.new(), Engine.new()
        ]
    }

    accept(visitor) {
        for (element in _elements) element.accept(visitor)
        visitor.visit(this)
    }
}

class CarElementDoVisitor is CarElementVisitor {
    construct new() {}

    visit(obj) {
        if (obj is Body) {
            System.print("Moving my body")
        } else if (obj is Car) {
            System.print("Starting my car")
        } else if (obj is Wheel) {
            System.print("Kicking my %(obj.name) wheel")
        } else if (obj is Engine) {
            System.print("Starting my engine")
        }
    }
}

class CarElementPrintVisitor is CarElementVisitor {
    construct new() {}

    visit(obj) {
        if ((obj is Body) || (obj is Car) || (obj is Engine)) {
            System.print("Visiting %(Str.lower(obj.type))")
        } else if (obj is Wheel) {
            System.print("Visiting %(obj.name) wheel")
        }
    }
}

var car = Car.new()
car.accept(CarElementPrintVisitor.new())
car.accept(CarElementDoVisitor.new())
Output:
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
Cookies help us deliver our services. By using our services, you agree to our use of cookies.