Visitor pattern: Difference between revisions
(Ada implementation with tagged types) |
m (→{{header|Ada}}: removed Method 2 after consideration) |
||
(One intermediate revision by the same user not shown) | |||
Line 36: | Line 36: | ||
=={{header|Ada}}== |
=={{header|Ada}}== |
||
An Ada implementation of the Wikipedia Java example. |
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: |
||
* <code>Vehicle_Elements</code> (spec + body) provides a base <code>Element</code> class for <code>Car</code> and its parts, as well as an <code>Element_Interface</code> for visitors. It's sufficiently abstract that you could, in principle, easily define a <code>Bicycle</code>, or a <code>Truck</code>, or an <code>Airplane</code>. |
|||
===Method 1: via Tagged Types (the object-oriented approach)=== |
|||
* The elements of a <code>Car</code> are defined in: |
|||
Perhaps more packages than needed (7), which makes for quite a few files (specification + implementation). Only the `Visitors` files are long; the rest are much shorter. |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
:* <code>Bodies</code> |
|||
package Base is |
|||
:* <code>Engines</code> |
|||
:* <code>Wheels</code> |
|||
* <code>Car_Visitors</code> (spec + body) provides an implementation of <code>Element_Visitor</code> for <code>Cars</code>, defining two visitors: |
|||
type Base_Record is abstract tagged null record; |
|||
:* <code>Perform_Visitor</code> |
|||
:* <code>Print_Visitor</code> |
|||
* <code>Cars</code> (spec + body) "builds" a <code>Car</code> from its various parts and overrides <code>Accept_Visitor</code>. |
|||
* <code>Visitor_Pattern</code> instantiates a car and invokes both visitors on it. |
|||
====<code>Vehicle_Elements</code>==== |
|||
<syntaxhighlight lang="ada">private with Ada.Strings.Unbounded; |
|||
package Vehicle_Elements is |
|||
end Base; |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
-- Forward declaration for visitor operation parameter |
|||
limited with Base; |
|||
type Element is tagged; |
|||
limited with Bodies; |
|||
limited with Cars; |
|||
limited with Engines; |
|||
limited with Wheels; |
|||
-- Generic visitor interface |
|||
package Visitors is |
|||
type Element_Visitor is interface; |
|||
-- Interface visiting procedure |
|||
type Visitor is abstract tagged null record; |
|||
procedure Visit |
|||
(Self : Element_Visitor; |
|||
Part : in out Vehicle_Elements.Element'Class) is abstract; |
|||
-- Base class type for all car things |
|||
procedure Visit_Base |
|||
type Element is abstract tagged private; |
|||
(Self : Visitor; Dest : Base.Base_Record'Class) is null; |
|||
-- Using 'Class here so I can provide a generic base class constructor |
|||
procedure Visit_Body (Self : Visitor; Dest : Bodies.Body_Record'Class); |
|||
-- Name - Name of the part: "Body", "Engine", "Wheel" |
|||
procedure Visit_Car (Self : Visitor; Dest : Cars.Car_Record'Class); |
|||
-- NOTE: When using to make an aggregate, type convert the result of this |
|||
procedure Visit_Engine (Self : Visitor; Dest : Engines.Engine_Record'Class); |
|||
-- operation to the Element type |
|||
procedure Visit_Wheel (Self : Visitor; Dest : Wheels.Wheel_Record'Class); |
|||
function Make (Name : String) return Element'Class; |
|||
-- To get the supplied name |
|||
type Perform is new Visitor with null record; |
|||
function Name (Self : Element'Class) return String; |
|||
type Print is new Visitor with null record; |
|||
-- This procedure calls Visitor.Visit(Self) by default |
|||
-- We can't call it `Accept` because `accept` is an Ada keyword... |
|||
(Self : Perform; Dest : Bodies.Body_Record'Class); |
|||
procedure Accept_Visitor |
|||
(Self : |
(Self : in out Element; Visitor : Element_Visitor'Class); |
||
private |
|||
overriding procedure Visit_Car |
|||
(Self : Perform; Dest : Cars.Car_Record'Class); |
|||
overriding procedure Visit_Car (Self : Print; Dest : Cars.Car_Record'Class); |
|||
use Ada.Strings.Unbounded; |
|||
overriding procedure Visit_Engine |
|||
(Self : Perform; Dest : Engines.Engine_Record'Class); |
|||
overriding procedure Visit_Engine |
|||
(Self : Print; Dest : Engines.Engine_Record'Class); |
|||
type Element is abstract tagged record |
|||
overriding procedure Visit_Wheel |
|||
Name : Unbounded_String; |
|||
(Self : Perform; Dest : Wheels.Wheel_Record'Class); |
|||
end record; |
|||
overriding procedure Visit_Wheel |
|||
(Self : Print; Dest : Wheels.Wheel_Record'Class); |
|||
end |
end Vehicle_Elements; |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
<syntaxhighlight lang="ada"> |
<syntaxhighlight lang="ada">package body Vehicle_Elements is |
||
-- Need a non abstract type to actually work in the Make function |
|||
with Ada.Text_IO; |
|||
type Factory is new Element with null record; |
|||
with Ada.Strings.Unbounded; |
|||
use all type Ada.Strings.Unbounded.Unbounded_String; |
|||
function Make (Name : String) return Element'Class is |
|||
with Wheels; |
|||
(Factory'(Name => To_Unbounded_String (Name))); |
|||
function Name (Self : Element'Class) return String is |
|||
package body Visitors is |
|||
(To_String (Self.Name)); |
|||
procedure Accept_Visitor |
|||
package IO renames Ada.Text_IO; |
|||
(Self : in out Element; Visitor : Element_Visitor'Class) |
|||
procedure Visit_Body (Self : Visitor; Dest : Bodies.Body_Record'Class) is |
|||
begin |
|||
Self.Visit_Body (Dest); |
|||
end Visit_Body; |
|||
overriding procedure Visit_Body |
|||
(Self : Perform; Dest : Bodies.Body_Record'Class) |
|||
is |
is |
||
begin |
begin |
||
Visitor.Visit (Self); |
|||
end |
end Accept_Visitor; |
||
end Vehicle_Elements; |
|||
overriding procedure Visit_Body |
|||
</syntaxhighlight> |
|||
(Self : Print; Dest : Bodies.Body_Record'Class) |
|||
====<code>Car_Visitors</code>==== |
|||
is |
|||
<syntaxhighlight lang="ada">with Vehicle_Elements; |
|||
begin |
|||
IO.Put_Line ("Visiting Body"); |
|||
end Visit_Body; |
|||
package Car_Visitors is |
|||
procedure Visit_Car (Self : Visitor; Dest : Cars.Car_Record'Class) is |
|||
begin |
|||
Self.Visit_Car (Dest); |
|||
end Visit_Car; |
|||
type Print_Visitor is new Vehicle_Elements.Element_Visitor with null record; |
|||
overriding procedure Visit_Car |
|||
overriding procedure Visit |
|||
(Self : Perform; Dest : Cars.Car_Record'Class) |
|||
(Self : Print_Visitor; Part : in out Vehicle_Elements.Element'Class); |
|||
is |
|||
begin |
|||
IO.Put_Line ("Starting my car"); |
|||
end Visit_Car; |
|||
type Perform_Visitor is |
|||
overriding procedure Visit_Car (Self : Print; Dest : Cars.Car_Record'Class) |
|||
new Vehicle_Elements.Element_Visitor with null record; |
|||
is |
|||
overriding procedure Visit |
|||
begin |
|||
(Self : Perform_Visitor; Part : in out Vehicle_Elements.Element'Class); |
|||
IO.Put_Line ("Visiting Car"); |
|||
end Visit_Car; |
|||
end Car_Visitors; |
|||
procedure Visit_Engine (Self : Visitor; Dest : Engines.Engine_Record'Class) |
|||
</syntaxhighlight> |
|||
is |
|||
<syntaxhighlight lang="ada">with Ada.Text_IO; use Ada.Text_IO; |
|||
begin |
|||
with Bodies; |
|||
Self.Visit_Engine (Dest); |
|||
with Engines; |
|||
end Visit_Engine; |
|||
with Wheels; |
|||
with Cars; |
|||
package body Car_Visitors is |
|||
overriding procedure Visit_Engine |
|||
(Self : Perform; Dest : Engines.Engine_Record'Class) |
|||
is |
|||
begin |
|||
IO.Put_Line ("Revving my Engine"); |
|||
end Visit_Engine; |
|||
overriding procedure |
overriding procedure Visit |
||
(Self : |
(Self : Print_Visitor; Part : in out Vehicle_Elements.Element'Class) |
||
is |
is |
||
begin |
begin |
||
Put_Line ("Visiting " & Part.Name); |
|||
end |
end Visit; |
||
overriding procedure Visit |
|||
procedure Visit_Wheel (Self : Visitor; Dest : Wheels.Wheel_Record'Class) is |
|||
(Self : Perform_Visitor; Part : in out Vehicle_Elements.Element'Class) |
|||
begin |
|||
Self.Visit_Wheel (Dest); |
|||
end Visit_Wheel; |
|||
overriding procedure Visit_Wheel |
|||
(Self : Perform; Dest : Wheels.Wheel_Record'Class) |
|||
is |
is |
||
begin |
begin |
||
if Part in Cars.Car then |
|||
IO.Put_Line ("Rolling my " & To_String (Dest.Name) & " wheel"); |
|||
Put_Line ("Starting the " & Part.Name); |
|||
end Visit_Wheel; |
|||
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; |
|||
overriding procedure Visit_Wheel |
|||
(Self : Print; Dest : Wheels.Wheel_Record'Class) |
|||
is |
|||
begin |
|||
IO.Put_Line ("Visiting " & To_String (Dest.Name) & " wheel"); |
|||
end Visit_Wheel; |
|||
end Visitors; |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
====The parts of a <code>Car</code>==== |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
<syntaxhighlight lang="ada">with Vehicle_Elements; |
|||
with Base; |
|||
limited with Visitors; |
|||
package Bodies is |
package Bodies is |
||
type |
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; |
|||
procedure Visit (Self : Body_Record; Visitor : Visitors.Visitor'Class); |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">with Vehicle_Elements; |
|||
package Engines is |
|||
private |
|||
type |
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 |
end Engines; |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
<syntaxhighlight lang="ada"> |
<syntaxhighlight lang="ada">with Vehicle_Elements; |
||
package Wheels is |
|||
with Visitors; |
|||
type Wheel is new Vehicle_Elements.Element with null record; |
|||
package body Bodies is |
|||
function Make (Name : String) return Wheel is |
|||
(Vehicle_Elements.Element (Vehicle_Elements.Make (Name & " Wheel")) with |
|||
null record); |
|||
end Wheels; |
|||
procedure Visit (Self : Body_Record; Visitor : Visitors.Visitor'Class) is |
|||
begin |
|||
Visitor.Visit_Body (Self); |
|||
end Visit; |
|||
end Bodies; |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
====<code>Cars</code>==== |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
<syntaxhighlight lang="ada">private with Wheels; |
|||
with Base; |
|||
with Bodies; |
with Bodies; |
||
with Engines; |
with Engines; |
||
with |
with Vehicle_Elements; |
||
limited with Visitors; |
|||
package Cars is |
package Cars is |
||
type |
type Car is new Vehicle_Elements.Element with private; |
||
overriding procedure Accept_Visitor |
|||
procedure Visit (Self : Car_Record; Visitor : Visitors.Visitor'Class); |
|||
(Self : in out Car; Visitor : Vehicle_Elements.Element_Visitor'Class); |
|||
function |
function Make return Car; |
||
private |
private |
||
type Wheel_Position is (Left_Front, Right_Front, Left_Back, Right_Back); |
|||
type Wheel_Array is array (1 .. 4) of Wheels.Wheel_Record; |
|||
type |
type Wheel_Array is array (Wheel_Position) of Wheels.Wheel; |
||
Bod : Bodies.Body_Record; |
|||
type Car is new Vehicle_Elements.Element with record |
|||
Eng : Engines.Engine_Record; |
|||
Car_Body : Bodies.Car_Body; |
|||
Engine : Engines.Engine; |
|||
All_Wheels : Wheel_Array; |
|||
end record; |
end record; |
||
Line 242: | Line 228: | ||
</syntaxhighlight> |
</syntaxhighlight> |
||
<syntaxhighlight lang="ada">pragma Ada_2022; |
<syntaxhighlight lang="ada">pragma Ada_2022; |
||
with Ada.Strings.Unbounded; |
|||
with Visitors; |
|||
package body Cars is |
package body Cars is |
||
function Make return Car is |
|||
subtype Unbounded_String is Ada.Strings.Unbounded.Unbounded_String; |
|||
(Vehicle_Elements.Element (Vehicle_Elements.Make ("Car")) with |
|||
use all type Unbounded_String; |
|||
Car_Body => Bodies.Make, Engine => Engines.Make, |
|||
All_Wheels => |
|||
(for Wheel in Wheel_Position => Wheels.Make (Wheel'Image))); |
|||
overriding procedure Accept_Visitor |
|||
procedure Visit (Self : Car_Record; Visitor : Visitors.Visitor'Class) is |
|||
(Self : in out Car; Visitor : Vehicle_Elements.Element_Visitor'Class) |
|||
is |
|||
begin |
begin |
||
Vehicle_Elements.Element (Self).Accept_Visitor (Visitor); |
|||
Self.Car_Body.Accept_Visitor (Visitor); |
|||
Self.Engine.Accept_Visitor (Visitor); |
|||
for Wheel of Self. |
for Wheel of Self.All_Wheels loop |
||
Wheel.Accept_Visitor (Visitor); |
|||
end loop; |
end loop; |
||
end |
end Accept_Visitor; |
||
function Initialize return Car_Record is |
|||
Result : Car_Record; |
|||
begin |
|||
Result.Whs := |
|||
[Wheels.Initialize (To_Unbounded_String ("front left")), |
|||
Wheels.Initialize (To_Unbounded_String ("front right")), |
|||
Wheels.Initialize (To_Unbounded_String ("back left")), |
|||
Wheels.Initialize (To_Unbounded_String ("back right"))]; |
|||
return Result; |
|||
end Initialize; |
|||
end Cars; |
end Cars; |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
====Putting it all together==== |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
<syntaxhighlight lang="ada">with Cars; |
|||
with |
with Car_Visitors; |
||
limited with Visitors; |
|||
package Engines is |
|||
type Engine_Record is new Base.Base_Record with private; |
|||
procedure Visit (Self : Engine_Record; Visitor : Visitors.Visitor'Class); |
|||
private |
|||
type Engine_Record is new Base.Base_Record with null record; |
|||
end Engines; |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
with Visitors; |
|||
package body Engines is |
|||
procedure Visit (Self : Engine_Record; Visitor : Visitors.Visitor'Class) is |
|||
begin |
|||
Visitor.Visit_Engine (Self); |
|||
end Visit; |
|||
end Engines; |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
with Ada.Strings.Unbounded; |
|||
with Base; |
|||
limited with Visitors; |
|||
package Wheels is |
|||
subtype Unbounded_String is Ada.Strings.Unbounded.Unbounded_String; |
|||
use all type Unbounded_String; |
|||
type Wheel_Record is new Base.Base_Record with private; |
|||
procedure Visit (Self : Wheel_Record; Visitor : Visitors.Visitor'Class); |
|||
function Initialize (Name : Unbounded_String) return Wheel_Record; |
|||
function Name (Me : Wheel_Record) return Unbounded_String; |
|||
private |
|||
type Wheel_Record is new Base.Base_Record with record |
|||
My_Name : Unbounded_String; |
|||
end record; |
|||
end Wheels; |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
with Visitors; |
|||
package body Wheels is |
|||
procedure Visit (Self : Wheel_Record; Visitor : Visitors.Visitor'Class) is |
|||
begin |
|||
Visitor.Visit_Wheel (Self); |
|||
end Visit; |
|||
function Initialize (Name : Unbounded_String) return Wheel_Record is |
|||
(My_Name => Name); |
|||
function Name (Me : Wheel_Record) return Unbounded_String is (Me.My_Name); |
|||
end Wheels; |
|||
</syntaxhighlight> |
|||
<syntaxhighlight lang="ada">pragma Ada_2022; |
|||
with Cars; |
|||
with Visitors; |
|||
procedure Visitor_Pattern is |
procedure Visitor_Pattern is |
||
Car : Cars. |
Car : Cars.Car := Cars.Make; |
||
Printer : Car_Visitors.Print_Visitor; |
|||
Performer : aliased Visitors.Perform; |
|||
Performer : Car_Visitors.Perform_Visitor; |
|||
Printer : aliased Visitors.Print; |
|||
begin |
begin |
||
Car. |
Car.Accept_Visitor (Printer); |
||
Car. |
Car.Accept_Visitor (Performer); |
||
end Visitor_Pattern; |
end Visitor_Pattern; |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
===Method 2: Discriminated Types=== |
|||
=={{header|Julia}}== |
=={{header|Julia}}== |
||
<syntaxhighlight lang="julia">abstract type CarElementVisitor end |
<syntaxhighlight lang="julia">abstract type CarElementVisitor end |
Latest revision as of 21:04, 5 July 2024
- 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 baseElement
class forCar
and its parts, as well as anElement_Interface
for visitors. It's sufficiently abstract that you could, in principle, easily define aBicycle
, or aTruck
, or anAirplane
.- The elements of a
Car
are defined in:
Bodies
Engines
Wheels
Car_Visitors
(spec + body) provides an implementation ofElement_Visitor
forCars
, defining two visitors:
Perform_Visitor
Print_Visitor
Cars
(spec + body) "builds" aCar
from its various parts and overridesAccept_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;
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.
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
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