Fact-checked by Grok 2 weeks ago

Double dispatch

Double dispatch is a technique in object-oriented programming (OOP) that enables the selection and execution of a method based on the runtime types of two objects involved in an operation, extending the standard single-dispatch mechanism where method selection depends solely on the receiver object's type. This approach simulates limited multiple dispatch in languages that natively support only single dispatch, such as Java or C++, by chaining two method calls: the first dispatches on the receiver's type, and the second on the argument's type. For instance, in handling binary arithmetic operations like addition between an Integer and a Float, the Integer object might invoke a type-specific method on the Float (e.g., sumFromInteger:), ensuring the correct behavior for that type pair without relying on runtime type checks. Originating in dynamically typed languages like Smalltalk, double dispatch addresses challenges in extending operations across class hierarchies without violating the open-closed principle, which advocates for software entities open to extension but closed to modification. It is prominently featured in the Visitor design pattern, one of the 23 patterns outlined in the seminal : Elements of Reusable Object-Oriented Software by , Richard Helm, Ralph Johnson, and John Vlissides, where it allows operations on object structures (e.g., abstract syntax trees) to be defined externally to the elements being visited, facilitating clean . In practice, double dispatch is essential for scenarios involving polymorphic interactions, such as geometric computations (e.g., intersection between shapes) or UI component rendering, where the outcome depends on the combined types of interacting entities. While powerful for maintaining extensibility in single-dispatch environments, double dispatch requires careful implementation to avoid code duplication across classes; for each new type added to one hierarchy, corresponding methods must be defined in the other, potentially leading to a in larger systems. Languages supporting native , like or , render double dispatch unnecessary by directly selecting methods based on all argument types at . Nonetheless, its elegance in bridging polymorphism gaps continues to influence modern practices, including in frameworks for and event handling.

Overview

Definition

Double dispatch is a technique in that selects the appropriate implementation based on the runtime types of two arguments, rather than just one as in single dispatch. This mechanism allows for more flexible polymorphism by considering the dynamic types of both the (typically the object on which the is called) and passed to it, enabling behavior that adapts to interactions between different object types. Key characteristics of double dispatch include its reliance on multiple parameters for polymorphic resolution, often implemented through method delegation—where the first dispatch selects a based on the receiver's type, and the second dispatch occurs within that based on the argument's type—or via explicit type checks. This approach is particularly useful in languages with single-dispatch semantics, such as or C++, to simulate multi-argument polymorphism without native support. Unlike , which generalizes the concept to select methods based on the runtime types of any number of arguments, double dispatch is limited to exactly two arguments, making it a specific instance of the broader paradigm. The following illustrates a simple double dispatch scenario for calculating the area of in different contexts, such as rendering or , where the dispatch depends on both the shape type and the context type:
abstract class Shape {
    abstract double area(Context ctx);
}

class Circle extends Shape {
    private double radius;
    double area(Context ctx) {
        return ctx.computeCircleArea(this);
    }
}

class Rectangle extends Shape {
    private double width, height;
    double area(Context ctx) {
        return ctx.computeRectangleArea(this);
    }
}

abstract class Context {
    abstract double computeCircleArea([Circle](/page/Circle) c);
    abstract double computeRectangleArea([Rectangle](/page/Rectangle) r);
}

class RenderingContext extends Context {
    double computeCircleArea([Circle](/page/Circle) c) {
        return Math.PI * c.[radius](/page/Radius) * c.[radius](/page/Radius);  // Standard area
    }
    double computeRectangleArea([Rectangle](/page/Rectangle) r) {
        return r.width * r.[height](/page/Height);  // Standard area
    }
}

class AnalysisContext extends Context {
    double computeCircleArea(Circle c) {
        return Math.PI * c.radius * c.radius * 1.1;  // Adjusted for analysis
    }
    double computeRectangleArea(Rectangle r) {
        return r.width * r.height * 1.1;  // Adjusted for analysis
    }
}
In this example, calling shape.area(new [RenderingContext](/page/RenderingContext)()) dispatches first on the Shape type to invoke area, which then dispatches on the [Context](/page/Context) type to select the specific area computation method.

Historical Development

The concept of double dispatch emerged in the late within early languages, particularly Smalltalk, where it addressed the need for polymorphic operations involving multiple argument types, such as arithmetic expressions. introduced the technique in 1986, describing it as "multiple polymorphism" and demonstrating its use for handling operations like addition between different number types in Smalltalk-80, enabling more flexible and extensible code without relying solely on single dispatch. Concurrently, the community advanced related ideas through the development of mechanisms. Daniel G. Bobrow and colleagues pioneered this in CommonLoops (1983–1986) and formalized it in the Object System (CLOS) specification of 1988, where generic functions dispatch based on the types of multiple arguments, influencing subsequent implementations of double dispatch in dynamic languages. In the 1990s, as matured and static languages like C++ gained prominence, double dispatch was adapted through to compensate for the lack of native support. The influential book Design Patterns: Elements of Reusable Object-Oriented Software by , Richard Helm, Ralph Johnson, and John Vlissides (1994)—often called the "Gang of Four" book—popularized the , which employs double dispatch to perform operations across class hierarchies without modifying existing classes, marking a key milestone in its adoption for C++ and similar languages. In Eiffel, developed by during the same period, agent-based approaches and polymorphic routines were explored to simulate double dispatch, allowing interactions in statically typed environments while leveraging Eiffel's contract-based design principles. By the early 2000s, double dispatch via the had integrated into mainstream libraries and frameworks for and C++, such as tree traversal utilities and components, establishing it as a standard tool for extensible designs in . An empirical study in 2008 highlighted its practical use alongside alternatives like full in languages such as CLOS, underscoring its enduring role in single-dispatch systems. Post-2010, the technique stabilized as a mature pattern, with no substantial innovations altering its core principles, though native in newer languages like built upon its foundational ideas without supplanting double dispatch in legacy contexts.

Dispatch Mechanisms in OOP

Single Dispatch

Single dispatch is the standard method resolution mechanism in most object-oriented programming languages, where the implementation of a method call is determined at runtime based solely on the dynamic type of the receiver object, also known as the target or 'this/self' argument. This enables runtime polymorphism, allowing subclasses to provide specialized implementations of methods declared in their superclasses without altering the calling code. It forms the foundation of dynamic binding in languages like , C++, and , where method selection depends only on the receiver's type, not on the types of other arguments. The process operates through dynamic binding. In statically typed languages such as C++ and , this is commonly implemented using virtual method tables (vtables), which are data structures associated with each in the inheritance hierarchy. A vtable is an of pointers to the functions for that ; each object contains a hidden pointer (vptr) to its 's vtable. Upon invocation of a method, the uses the object's vptr to access the vtable and selects the corresponding to the method's offset, ensuring the correct overridden implementation is executed based on the object's actual runtime type. This mechanism supports efficient subtype polymorphism while resolving calls late, typically with a constant-time lookup cost. Consider a basic example illustrating single dispatch in an hierarchy for drawing shapes:
[class](/page/Class) Shape {
    [virtual](/page/Virtual) [method](/page/Method) [draw](/page/Draw!)() {
        // [Generic](/page/Generic) shape drawing
    }
}

[class](/page/Class) Circle extends Shape {
    override [method](/page/Method) [draw](/page/Draw!)() {
        // Specific circle drawing logic
    }
}

Shape s = new Circle();
s.[draw](/page/Draw!)();  // Resolves to Circle's draw() at runtime via vtable lookup
In this setup, the call to draw() on the Shape reference dispatches to the Circle implementation because the receiver's dynamic type is Circle, demonstrating how single dispatch achieves polymorphic behavior without compile-time knowledge of the exact type. A key limitation of single dispatch arises when method behavior needs to vary based on the types of multiple arguments, such as in binary operations like shape intersection (e.g., determining how a and interact). Since dispatch considers only the receiver's type, it cannot automatically select implementations polymorphic on argument types, often requiring manual type checks (e.g., conditional statements) or auxiliary mechanisms, which can result in brittle, less extensible code that duplicates logic across classes.

Double Dispatch

Double dispatch extends the single-dispatch mechanism of , which selects a based solely on the runtime type of the , by incorporating the runtime type of an additional argument to determine the appropriate implementation. This enables polymorphic behavior dependent on the dynamic types of two objects, resolving limitations in scenarios requiring interactions between hierarchies. The core technique employs a double-dispatch : the first object (the receiver) calls a delegating on the second object (the argument), passing a reference to itself; the second object then invokes a callback whose implementation is selected based on the type of the first object. This creates a chain of delegations that achieves the dual-type resolution without native multiple-dispatch support. Implementation strategies vary, including delegation chains via calls for polymorphism, explicit accept/visit pairs to the dispatch explicitly, or type (RTTI) for explicit type querying and casting in languages lacking built-in multimethods. A generic pseudocode illustration of this setup, using abstract classes and interfaces for the dispatch hierarchy, is as follows:
pseudocode
// Abstract base for elements
abstract class Element {
    abstract accept(Visitor v);
}

// Concrete element subclass
class ConcreteElement extends Element {
    void accept(Visitor v) {
        v.visitConcreteElement(this);
    }
}

// Visitor interface with type-specific methods
interface Visitor {
    void visitConcreteElement(Element e);
    // Overloads for other concrete types
}

// Concrete visitor implementation
class ConcreteVisitor implements Visitor {
    void visitConcreteElement(Element e) {
        // Type-specific operation based on e's runtime type and visitor's behavior
    }
}

// Usage: Triggers double dispatch
Element elem = new ConcreteElement();
Visitor vis = new ConcreteVisitor();
elem.accept(vis);  // Dispatches to vis.visitConcreteElement(elem)
In statically typed languages, ensuring type safety poses challenges, as implementations must rely on abstract base classes to define polymorphic interfaces and abstract methods to enforce overrides, preventing type errors at compile time while allowing runtime dispatch. Without such structures, reliance on RTTI or reflection can introduce fragility and undermine static guarantees.

Applications

Visitor Pattern

The Visitor pattern is a behavioral introduced in the seminal work by Gamma et al., where operations on elements of an object structure are separated from the objects themselves, allowing algorithms to be defined independently of the classes they manipulate. It represents an operation to be performed on the elements of an object structure, enabling some other object—the —to carry out the work without altering the classes of those elements. This pattern leverages double dispatch to route the operation to the correct implementation based on both the type of the element and the type of the visitor. In its structure, the pattern centers on an Element hierarchy, where each element class declares an accept(Visitor v) method that takes a visitor as an argument. The Visitor interface defines a visit() operation for each concrete element type in the hierarchy, ensuring that visitors can handle all possible element variants. ConcreteVisitor subclasses implement these visit() methods to provide specific behaviors tailored to each element type. When an element invokes accept(v), it calls v.visit(this), dispatching control to the visitor's appropriate visit() method based on the element's concrete type, thus realizing the double dispatch. An ObjectStructure—such as a composite or collection—maintains the elements and provides traversal methods that invoke accept() on each element in turn. ConcreteElement classes implement the accept() method by forwarding to the visitor, while Client code instantiates a concrete visitor and initiates traversal via the object structure. Textually, the key participants can be represented as follows:
  • Visitor: visitConcreteElementA(elementA: ConcreteElementA); visitConcreteElementB(elementB: ConcreteElementB)
  • ConcreteVisitor: Implements Visitor operations with specific logic
  • : accept(visitor: Visitor)
  • ConcreteElementA: accept(visitor: Visitor) { visitor.visitConcreteElementA(this) }
  • ObjectStructure: traverse(visitor: Visitor) { for each element in structure: element.accept(visitor) }
This arrangement ensures that new operations can be introduced by subclassing Visitor without modifying the Element classes, thereby conforming to the open-closed principle by being open for extension but closed for modification.

Polymorphic Operations

Double dispatch enables polymorphic operations that depend on the types of two objects, allowing the selection of appropriate behavior for interactions between elements of a without relying on explicit type checking. This technique is particularly useful in scenarios where operations must vary based on both operands, such as interactions between geometric entities or handling events between game objects. One common idiom is in game development, where double dispatch determines the specific for detecting overlaps between two polymorphic objects, such as a ball and a wall or two vehicles. For instance, when two shapes potentially collide, the first object's method invokes a type-specific handler on the second object, ensuring the correct collision resolution—such as elastic bounce for spheres or inelastic for rectangles—is applied dynamically. This approach avoids cascading if-else statements based on type tags, which can become unwieldy as the number of shape types grows. Another application arises in serialization processes, where double dispatch facilitates the customization of how objects of different types are persisted or transmitted. In Java's framework, for example, an ObjectOutputStream dispatches to a type-specific writeObject on the target object, allowing each class to define its own serialization logic while remaining from the stream's . This supports efficient handling of type pairs, such as serializing a complex graph involving mixed object types, without requiring the serializer to know every possible class detail upfront. A representative involves mathematical operations between , such as the of a and a . Double dispatch selects the optimal —perhaps an analytic for circle-rectangle overlaps numerical methods for more complex pairs—by first dispatching on the first shape's type and then on the second's, enabling precise area calculations or overlap visualizations in graphics applications. This method ensures extensibility: adding a new shape type, like a , requires only implementing intersection handlers for existing types, without modifying unrelated code. The advantages of double dispatch in these polymorphic operations include cleaner, more maintainable code compared to type-switch cascades, which proliferate with hierarchy expansion and risk missing cases. It promotes extensibility for new type combinations, as each can independently define its interactions, fostering open-closed principle adherence in object-oriented design. Double dispatch serves as a lightweight subset of tailored to operations, where the latter generalizes to more arguments by selecting methods based on all involved types' characteristics. While natively supports this in languages like , double dispatch emulates it in single-dispatch environments for pairwise interactions, such as the binary collisions or intersections described.

Examples

Ruby

Ruby's dynamic typing and object model facilitate double dispatch primarily through the , leveraging and method resolution rather than explicit type declarations or virtual tables. This approach allows methods to be selected based on the types of two objects at , using mechanisms like respond_to? for type checking or method_missing for fallback dispatching, though the provides a clean, idiomatic implementation without requiring library extensions. A representative example of double dispatch in Ruby involves rendering shapes with different colors, where the rendering behavior depends on both the shape type (e.g., or square) and the color type (e.g., red or blue). Here, shapes act as elements that accept color visitors, dispatching to specific methods in the visitor based on the shape's class. The following code demonstrates this using the :
ruby
# Abstract Shape (Element)
class Shape
  def accept(color_visitor)
    raise NotImplementedError, "#{self.class} must implement accept"
  end
end

# Concrete Shapes
class Circle < Shape
  def accept(color_visitor)
    color_visitor.visit_circle(self)
  end

  def draw
    "Drawing a circle"
  end
end

class Square < Shape
  def accept(color_visitor)
    color_visitor.visit_square(self)
  end

  def draw
    "Drawing a square"
  end
end

# Abstract Color Visitor
class ColorVisitor
  def visit_circle(_circle)
    raise NotImplementedError, "#{self.class} must implement visit_circle"
  end

  def visit_square(_square)
    raise NotImplementedError, "#{self.class} must implement visit_square"
  end
end

# Concrete Color Visitors
class Red < ColorVisitor
  def visit_circle(circle)
    puts "#{circle.draw} in red (e.g., with red outline)"
  end

  def visit_square(square)
    puts "#{square.draw} in red (e.g., filled red)"
  end
end

class Blue < ColorVisitor
  def visit_circle(circle)
    puts "#{circle.draw} in blue (e.g., filled blue)"
  end

  def visit_square(square)
    puts "#{square.draw} in blue (e.g., with blue outline)"
  end
end

# Client code to demonstrate
shapes = [Circle.new, Square.new]
red = Red.new
blue = Blue.new

puts "Rendering with red:"
shapes.each { |shape| shape.accept(red) }

puts "\nRendering with blue:"
shapes.each { |shape| shape.accept(blue) }
This code outputs:
Rendering with red:
Drawing a circle in red (e.g., with red outline)
Drawing a square in red (e.g., filled red)

Rendering with blue:
Drawing a circle in blue (e.g., filled blue)
Drawing a square in blue (e.g., with blue outline)
In execution, double dispatch occurs as follows: When shape.accept(color_visitor) is called, Ruby first dispatches to the accept method on the specific shape instance (single dispatch on shape type). Inside accept, the shape invokes a type-specific method on the visitor (e.g., visit_circle for a Circle), enabling the second dispatch based on the color visitor's class. This runtime resolution ensures the appropriate rendering logic is selected without conditional checks, relying on Ruby's dynamic method lookup. Ruby's open classes further simplify this pattern, allowing methods to be added or modified at runtime to extend dispatch behavior across hierarchies without recompilation, contrasting with the static commitments in languages requiring virtual tables. This flexibility makes double dispatch feel more natural in Ruby, often requiring fewer boilerplate elements.

C++

In C++, double dispatch is typically implemented using the Visitor pattern, which relies on abstract base classes featuring pure virtual accept() methods in the element hierarchy and corresponding visit() methods in the visitor interface. This setup simulates double dispatch by leveraging C++'s virtual function mechanism: the accept() call dispatches dynamically based on the runtime type of the element, which then invokes the type-specific visit() method on the visitor object. The following example demonstrates double dispatch for geometric shapes, where a DrawingVisitor performs rendering operations on Rectangle and Circle objects without modifying their classes. The element hierarchy includes an abstract Shape base class with a pure virtual accept(Visitor&) method. Concrete shapes implement accept() to call the appropriate visitShape() overload on the visitor, passing this. The Visitor base class declares pure virtual visitRectangle() and visitCircle() methods, implemented in DrawingVisitor to output simple drawing commands. This achieves double dispatch by resolving the operation based on both the shape's dynamic type and the visitor's static type. To illustrate compile-time type safety, s are used in headers to resolve cyclic dependencies between the visitor and element classes, ensuring the compiler can verify types without full definitions initially. Shape.h
cpp
#ifndef SHAPE_H
#define SHAPE_H

class Visitor;  // [Forward declaration](/page/Forward_declaration)

class Shape {
public:
    virtual void accept(Visitor& v) = 0;
    virtual ~Shape() = default;
};

#endif
Rectangle.h
cpp
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include "Shape.h"
#include <iostream>

class [Visitor](/page/Visitor);  // Forward declaration

class Rectangle : [public](/page/Public) [Shape](/page/Shape) {
public:
    void accept([Visitor](/page/Visitor)& v) override;
};

#endif
Circle.h
cpp
#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"
#include <iostream>

class [Visitor](/page/Visitor);  // Forward declaration

class Circle : [public](/page/Public) [Shape](/page/Shape) {
public:
    void accept([Visitor](/page/Visitor)& v) override;
};

#endif
Visitor.h
cpp
#ifndef VISITOR_H
#define VISITOR_H

class Shape;
class Rectangle;
class Circle;  // Forward declarations

class Visitor {
public:
    virtual void visitRectangle(Rectangle& r) = 0;
    virtual void visitCircle([Circle](/page/Circle)& c) = 0;
    virtual ~Visitor() = default;
};

#endif
Rectangle.cpp
cpp
#include "Rectangle.h"
#include "Visitor.h"

void Rectangle::accept(Visitor& v) {
    v.visitRectangle(*this);
}
Circle.cpp
cpp
#include "Circle.h"
#include "Visitor.h"

void Circle::accept(Visitor& v) {
    v.visitCircle(*this);
}
DrawingVisitor.h
cpp
#ifndef DRAWINGVISITOR_H
#define DRAWINGVISITOR_H

#include "Visitor.h"
#include <iostream>

class [Rectangle](/page/Rectangle);
class [Circle](/page/Circle);

class [DrawingVisitor](/page/Visitor) : [public](/page/Public) [Visitor](/page/Visitor) {
public:
    void visitRectangle([Rectangle](/page/Rectangle)& r) override {
        std::cout << "Drawing a [rectangle](/page/Rectangle)." << std::endl;
    }

    void visitCircle([Circle](/page/Circle)& c) override {
        std::cout << "Drawing a [circle](/page/Circle)." << std::endl;
    }
};

#endif
main.cpp
cpp
#include "[Rectangle](/page/Rectangle).h"
#include "[Circle](/page/Circle).h"
#include "[DrawingVisitor](/page/Visitor).h"
#include <vector>

int main() {
    std::vector<Shape*> shapes;
    shapes.push_back(new [Rectangle](/page/Rectangle)());
    shapes.push_back(new [Circle](/page/Circle)());

    DrawingVisitor drawer;
    for ([Shape](/page/Shape)* shape : shapes) {
        shape->accept(drawer);
    }

    // Cleanup
    for ([Shape](/page/Shape)* shape : shapes) {
        delete shape;
    }

    return 0;
}
When executed, this program outputs:
Drawing a rectangle.
Drawing a circle.
The double dispatch mechanism relies on C++'s virtual function tables (vtables): each concrete shape class has a vtable entry for accept(), which the runtime uses to invoke the correct overridden accept() based on the object's dynamic type during the initial call (e.g., shape->accept(drawer)). This resolves to the specific visitXXX(*this) call, enabling the visitor to handle the element type polymorphically without relying on (RTTI), as the dispatch chain uses only virtual calls and overload resolution. A unique challenge in C++ arises from its static typing, which enforces strict compile-time checks and can lead to header cyclic dependencies between visitors and elements; forward declarations mitigate this by allowing incomplete types for parameter passing in methods, preserving without requiring mutual includes.

C#

In C#, double dispatch is commonly implemented using the , which leverages s to define the element hierarchy and visitor operations, enabling runtime selection of methods based on the types of both the element and the visitor. This approach relies on C#'s support for methods and implementations, where the Accept method in elements calls the appropriate Visit overload in the visitor, achieving the second level of polymorphism. Unlike C++, C# emphasizes s (e.g., IVisitor and IElement) for and explicit interface implementation to avoid boilerplate in base classes. A representative example demonstrates double dispatch in a file system context, where elements like File and Directory accept a PrintVisitor to perform type-specific printing operations. The following complete code uses .NET interfaces and virtual methods to traverse and print a simple directory structure.
csharp
using System;
using System.Collections.Generic;

// Interface for elements in the hierarchy
public interface IElement
{
    void Accept(IVisitor visitor);
}

// Concrete element: File
public class File : IElement
{
    public string Name { get; }
    public long Size { get; }

    public File(string name, long size)
    {
        Name = name;
        Size = size;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// Concrete element: Directory, which can contain other elements
public class Directory : IElement
{
    public string Name { get; }
    private List<IElement> Children { get; } = new List<IElement>();

    public Directory(string name)
    {
        Name = name;
    }

    public void Add(IElement element)
    {
        Children.Add(element);
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
        foreach (var child in Children)
        {
            child.Accept(visitor);
        }
    }
}

// Interface for visitors
public interface IVisitor
{
    void Visit(File file);
    void Visit(Directory directory);
}

// Concrete visitor: Prints elements with type-specific formatting
public class PrintVisitor : IVisitor
{
    public void Visit(File file)
    {
        Console.WriteLine($"File: {file.Name}, Size: {file.Size} bytes");
    }

    public void Visit(Directory directory)
    {
        Console.WriteLine($"Directory: {directory.Name}");
    }
}

// Usage example
class Program
{
    static void Main()
    {
        var root = new Directory("Root");
        root.Add(new File("document.txt", 1024));
        root.Add(new Directory("Subfolder"));
        root.Add(new File("image.jpg", 2048));

        var printVisitor = new PrintVisitor();
        root.Accept(printVisitor);
    }
}
When executed, this code produces output demonstrating double dispatch: the Accept call on a Directory invokes Visit(Directory), which prints the directory name and recursively dispatches to children, while File instances trigger Visit(File) for size-aware printing. The result is:
Directory: Root
File: document.txt, Size: 1024 bytes
Directory: Subfolder
File: image.jpg, Size: 2048 bytes
C#'s resolves virtual method calls at runtime via the (vtable), ensuring dispatch based on the actual object types during the Accept invocation, which supports the double dispatch mechanism without compile-time type knowledge of the . Garbage collection in the .NET runtime manages object lifetimes automatically, preventing dangling references during dispatch and allowing safe polymorphic operations across managed heaps, though it introduces minor overhead from generational collection pauses that do not directly alter dispatch logic. For simpler cases, the dynamic keyword in C# 4.0 and later enables double dispatch by deferring method resolution to runtime, binding based on argument types without explicit hierarchies, as seen in currency conversion scenarios where operations like USDToEUR are selected dynamically. This avoids the Visitor pattern's structure but sacrifices compile-time safety. Unique to C#, the Visitor pattern integrates with LINQ for querying element collections (e.g., selecting directories via Where(e => e is Directory)) or delegates for flexible visitor composition, allowing lambda-based operations like Action<IElement> to extend dispatch without subclassing.

Eiffel

Eiffel implements double dispatch through its external dispatch mechanism, which extends single dispatch to multiple arguments via creation procedures on deferred classes. This approach utilizes polymorphism inherent in deferred classes—abstract classes that define interfaces without full implementations—and routines as agents to enable dynamic selection of effective routines based on the runtime types of multiple objects. Unlike languages requiring runtime type identification, Eiffel's static type system resolves the dispatch at compile time for type safety. A classic illustration of double dispatch in Eiffel involves simulating collisions in a space game, such as between spaceships and asteroids. The design defines a base deferred class for moving objects and a deferred external dispatch class for collisions, with effective subclasses providing type-specific behaviors. This setup allows modular extension: new object types can be added by inheriting from the base and defining corresponding collision handlers without altering existing code.
eiffel
-- Base deferred class for moving objects
deferred class MOVING_OBJECT
feature
    x, y: REAL_64
        -- Position attributes
    move(dx, dy: REAL_64)
            -- Update position
        do
            x := x + dx
            y := y + dy
        end
end

-- Effective class for spaceship
class SHIP
inherit
    MOVING_OBJECT
feature
    -- Ship-specific features, e.g., [thrust](/page/Thrust)
end

-- Effective class for asteroid
class ASTEROID
inherit
    MOVING_OBJECT
feature
    -- Asteroid-specific features, e.g., spin
end

-- Deferred external dispatch class for collisions
deferred class COLLISION(o1, o2: MOVING_OBJECT)
create
    make
feature {NONE}
    make(a_o1, a_o2: like o1)
            -- Creation procedure for dispatch
        require
            valid_objects: a_o1 /= Void and a_o2 /= Void
        do
            o1 := a_o1
            o2 := a_o2
        ensure
            objects_set: o1 = a_o1 and o2 = a_o2
        end
feature
    outcome: [STRING](/page/STRING)
            -- Type-specific collision result
        deferred
        require
            objects_valid: o1 /= Void and o2 /= Void
        ensure
            result_not_void: Result /= Void
        end
end

-- Effective class for ship-asteroid collision
[class](/page/Class) SHIP_ASTEROID_COLLISION
inherit
    COLLISION
create
    make
[feature](/page/Feature) {NONE}
    make(a_ship: SHIP; a_asteroid: [ASTEROID](/page/Asteroid))
        do
            Precursor(a_ship, a_asteroid)
        end
[feature](/page/Feature)
    outcome: [STRING](/page/String)
        do
            Result := "The ship is destroyed by the asteroid."
        end
end

-- Additional effective classes for other combinations (e.g., SHIP_SHIP_COLLISION, ASTEROID_ASTEROID_COLLISION)
-- would be defined similarly for full coverage.
To simulate a collision, the following setup creates instances and invokes the dispatched routine:
eiffel
-- Simulation setup
local
    ship: SHIP
    asteroid: ASTEROID
    collision: COLLISION
do
    create ship
    create asteroid
    ship.move(1.0, 2.0)  -- Position ship
    asteroid.move(1.0, 2.0)  -- Position asteroid at same location
    create collision.make(ship, asteroid)  -- Dispatches to SHIP_ASTEROID_COLLISION
    io.put_string(collision.outcome)
    -- Output: The ship is destroyed by the asteroid.
end
In this example, the external dispatch on the make creation procedure selects the appropriate effective class (e.g., SHIP_ASTEROID_COLLISION) based on the actual types of o1 and o2, enabling the polymorphic call to outcome. Eiffel's integrates seamlessly, enforcing preconditions (e.g., non-null objects) and postconditions (e.g., non-void results) across the dispatch hierarchy to ensure reliable behavior and catch violations at if assertions are enabled. This static eliminates the need for (RTTI), avoiding dynamic casts and potential exceptions. The mechanism's emphasis on reusability stems from its support for open extension: adding a new moving object type requires only new effective collision classes, preserving the purity of the original . Its provability aligns with Eiffel's object-oriented principles, as the type-safe dispatch and contract verification facilitate formal proofs of correctness, making it ideal for safety-critical applications. This fits Eiffel's of disciplined, verifiable , as advocated by its designer.

References

  1. [1]
    [PDF] 3/24/03 Doc 13 Double Dispatch slide # 1 CS 535 Object-Oriented ...
    Mar 24, 2003 · 3/24/03. Doc 13 Double Dispatch slide # 1. CS 535 Object-Oriented Programming & Design. Spring Semester, 2003. Doc 13 Double Dispatch.
  2. [2]
    [PDF] CSE341: Programming Languages Lecture 22 OOP vs. Functional ...
    • In object-oriented programming, break programs down into classes that give ... – No help in our example, so still code up double-dispatch manually.
  3. [3]
    COMP 105 Smalltalk Homework
    Apr 22, 2024 · The purpose of this assignment is to help you get acquainted with pure object-oriented programming. ... double dispatch. You will find a ...
  4. [4]
    [PDF] Visitor Design Pattern
    [2] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design patterns : elements of reusable object-oriented software. Addison-Wesley, Reading, Mass ...
  5. [5]
    Patterns in Practice: The Open Closed Principle - Microsoft Learn
    We can use a pattern called Double Dispatch to pull out the variation into the subclasses in such a way that we don't break existing interface contracts. For ...
  6. [6]
    Double Dispatch in DDD | Baeldung
    Mar 17, 2024 · Double dispatch is a technical term to describe the process of choosing the method to invoke based both on receiver and argument types.
  7. [7]
    Visitor and Double Dispatch - Refactoring.Guru
    Double dispatch is a trick that allows using dynamic binding alongside with overloaded methods. Here how it's done:
  8. [8]
    [PDF] Multiple Dispatch in Practice - Alex Potanin
    We can get an idea of to what degree double dispatch is used by measuring the occurrence of the double dispatch pattern. ... Object-oriented multi-methods in ...
  9. [9]
    A simple technique for handling multiple polymorphism
    A simple technique for handling multiple polymorphism · Constraint-bounded polymorphism: an expressive and practical type system for object-oriented languages.
  10. [10]
    Common Lisp Object System specification | ACM SIGPLAN Notices
    Common Lisp Object System specification. Authors: Daniel G. Bobrow.
  11. [11]
    [PDF] CFIXX: Object Type Integrity for C++
    Feb 18, 2018 · The final step of either attack is to overwrite the virtual table pointer of an existing object with a pointer to the attacker chosen virtual ...
  12. [12]
    [PDF] Yet Another Object-Oriented Single and Multiple Dispatch Mechanism
    A distinctive characteristic of object-oriented programming languages is the single dynamic dispatch mechanism: the method to be executed depends on the ...
  13. [13]
    Double dispatch in C++ | Request PDF - ResearchGate
    Aug 6, 2025 · Double dispatch is the ability to dynamically select a method not only according to the run-time type of the receiver (single dispatch), ...
  14. [14]
    [PDF] The VISITOR Pattern as a Reusable, Generic, Type-Safe Component
    Abstract. The VISITOR design pattern shows how to separate the structure of an object hierarchy from the behaviour of traver- sals over that hierarchy.
  15. [15]
    Design Patterns: Elements of Reusable Object-Oriented Software
    Design Patterns is a modern classic that introduces what patterns are and how they can help you design object-oriented software.
  16. [16]
    Multiple Dispatch and Double Dispatch - CodeProject
    Aug 19, 2011 · Double dispatch is a special form of multiple dispatch, which dispatches a function call to different concrete functions depending on the run- ...
  17. [17]
    How does double dispatch help collision detection?
    Dec 20, 2011 · Double-dispatch is a way to resolve a function call based on two of the function's arguments. In a collision detection scenario where you have ...
  18. [18]
    Double Dispatch - Java Performance Tuning
    In this case, the double dispatch pattern is well illustrated by the Serializable interface. It may come as a surprise that a simple methodless interface ...Missing: type pairs
  19. [19]
  20. [20]
    [PDF] Multimethods - Pearsoncmg.com
    A particularization of multiple dispatch for two objects is known as double dispatch ... For example, the Executor class for hatching shape intersections is as ...<|separator|>
  21. [21]
    Visitor in Ruby / Design Patterns - Refactoring.Guru
    Visitor pattern in Ruby. Full code example in Ruby with detailed comments ... Visitor and Double Dispatch. Learn more about Visitor. Navigation. Intro.<|control11|><|separator|>
  22. [22]
    The Visitor Pattern – MC++ BLOG - Modernes C++
    Nov 27, 2022 · Double dispatch, in the case of the visitor, is a ping/pong game between the element (car element) and the visitor. The car element applies a ...
  23. [23]
    interface keyword - C# reference - Microsoft Learn
    Oct 4, 2025 · Method dispatch for static abstract and static virtual methods declared in interfaces is resolved using the compile time type of an expression.
  24. [24]
    Working Programmer - Multiparadigmatic .NET, Part 8: Dynamic ...
    In this situation, however, we want to dispatch based on two types (C1 and C2)—what's sometimes called double dispatch. Traditional OOP has no great ...
  25. [25]
    External Dispatch in Eiffel
    As a proof-of-concept, an external dispatch mechanism proposal was adapted and implemented for Eiffel. This is the first quite complete compiler version for ...
  26. [26]
    I2E: Deferred Classes and Seamless Development - Eiffel.org
    Declaring a feature f as deferred in a class C expresses that there is no default implementation of f in C ; such implementations will appear in eventual ...Missing: dispatch | Show results with:dispatch
  27. [27]
    [PDF] Componentization - Chair of Software Engineering
    For example the Visitor pattern, which we'll use as an example for this ... The Visitor pattern implements a “double-dispatch” mechanism: Class. Inherits ...