OOP FundamentalsPolymorphismHard⏱️ ~4 min

Advanced Polymorphism: Double Dispatch and Visitor Pattern

The Limitation of Single Dispatch

Standard polymorphism in object-oriented languages uses single dispatch: the method executed depends on the runtime type of one object (the receiver). However, some problems require double dispatch, where the method depends on the types of two objects.

Example Problem: In a game, different collision behaviors occur based on both objects involved. A Spaceship colliding with an Asteroid is different from a Spaceship colliding with another Spaceship. Single dispatch cannot elegantly handle this without type checking.

Double Dispatch Solution

GameObject
+ collideWith(other: GameObject)
+ collideWithSpaceship(s: Spaceship)
+ collideWithAsteroid(a: Asteroid)
Spaceship
+ collideWith(other: GameObject)
+ collideWithSpaceship(s: Spaceship)
+ collideWithAsteroid(a: Asteroid)
Asteroid
+ collideWith(other: GameObject)
+ collideWithSpaceship(s: Spaceship)
+ collideWithAsteroid(a: Asteroid)
Double Dispatch Flow:
Spaceship.collideWith(other):
  other.collideWithSpaceship(this) // First dispatch on 'other' type

Asteroid.collideWithSpaceship(spaceship):
  // Handle Spaceship-Asteroid collision // Second dispatch

First dispatch: collideWith is called on the first object, dispatching based on its type. Second dispatch: That method calls back to the other object with a type-specific method (e.g., collideWithSpaceship), dispatching based on the second object's type. This achieves behavior selection based on both types without type checking.

Visitor Pattern: Structured Double Dispatch

The Visitor Pattern formalizes double dispatch for adding operations to a stable class hierarchy without modifying the classes. It is useful when you have a fixed set of types but frequently add new operations.

«interface»
ItemVisitor
+ visitBook(book: Book)
+ visitMagazine(mag: Magazine)
+ visitDVD(dvd: DVD)
LibraryItem
+ accept(visitor: ItemVisitor)

Use case: Library system needs to export items in different formats (JSON, XML, PDF). Instead of adding exportToJSON(), exportToXML(), exportToPDF() methods to each item class, create JSONExportVisitor, XMLExportVisitor, PDFExportVisitor implementations. Each item's accept method calls visitor.visitBook(this), enabling type-specific export logic in the visitor.

When to Use Visitor vs Standard Polymorphism

Use standard polymorphism when: Operations are core to the object's responsibility and rarely change. For example, calculateArea() for shapes is intrinsic and stable.

Use Visitor Pattern when: First, you have a stable class hierarchy that changes infrequently. Second, you need to add new operations frequently without modifying existing classes. Third, operations are external concerns (exporting, rendering, validation) rather than core behavior. However, if the class hierarchy changes often, Visitor becomes cumbersome because adding a new type requires updating all visitors.

Interview Tip: Visitor Pattern is rarely required in LLD interviews unless explicitly asked about adding operations without modifying classes. Know it conceptually, but prefer simpler polymorphism first.
💡 Key Takeaways
Single dispatch (standard polymorphism) selects methods based on one object's type
Double dispatch selects methods based on two objects' types, requiring callback pattern
Visitor Pattern formalizes double dispatch for adding operations to stable class hierarchies
Use Visitor when class hierarchy is stable but operations change frequently
Visitor becomes cumbersome if both hierarchy and operations change often
📌 Examples
1Double dispatch: Collision detection in games based on both colliding object types
2Visitor: Exporting library items in multiple formats (JSON, XML, PDF) without modifying item classes
3Visitor: Rendering shapes to different output targets (screen, printer, SVG)
← Back to Polymorphism Overview