Jump to: navigation, search

OTPattern/Double Dispatch

Intent

A method call should be dispatched in two (or more) dimensions.

Also Known As

In OOP this problem is typically solved by the Visitor design pattern.

Motivation

Commonly you'll have an inheritance hierarchy of data structures that implement some intrinsic operations. Secondly you'll want to implement certain aggregate operations on those data but those operations should be implemented outside the data classes. For invoking any operation two hops of dynamic dispatch are required: one selecting the method appropriate for the dynamic type of the data object involved and another hop appropriate for the actual operation to be invoked.

Structure

OTDouble-dispatch.png

While the base classes (the "data structure") form a regular Java inheritance hierarchy, each operation is defined by one team class with roles representing the base classes.

Participants

  • AbstractBase — root of the hierarchy defining the base data structure
  • ConcreteBaseA, ConcreteBaseB — specializations of AbstractBase
  • Operation — team representing an aggregate operation that operates on instances of the base data structure
  • AbstractRole, ConcreteRoleA, ConcreteRoleB — representations of corrensponding base classes for the operation. Note that roles also reflect the base inheritance hierarchy by a hierarchy of their own.
  • Operation1 — Specialization of Operation, overriding some of its roles.

Collaborations

The operation is invoked by a call to Operation.invoke(..) passing an instance of static type AbstractBase:

op.invoke(b)

Double dispatch now works as follows:

  1. method invoke is dispatched according to the dynamic type of op.
  2. the argument b is lifted to the best matching role type ("smart lifting" OTJLD §2.3.3).
  3. following the dynamic type of the role obtained by (2) method commonBehavior is dispatched to the most suitable role class.

Implementation

Given a regular hierarchy of Shapes:

public class Shape {/*...*/}
 
public class Square extends Shape {/*...*/}
 
public class Triangle extends Shape {/*...*/}

an operation "print" can be implemented by the following team:

public team class Printer {
    protected class PrintableShape playedBy Shape {
        protected void print() {
            System.out.println("Shape");
        }
    }
    protected class PrintableTriangle extends PrintableShape playedBy Triangle {
        protected void print() {
            System.out.println("Triangle");
        }               
    }
    protected class PrintableSquare extends PrintableShape playedBy Square {
        protected void print() {
            System.out.println("Square");
        }               
    }
    public void invoke(Shape as PrintableShape shape) {
        shape.print();
    }
}

This operation can be sub-classed to yield a German speaking printer:

public team class GermanPrinter extends Printer {
    protected class PrintableShape  {
        protected void print() {
            System.out.println("Form");
        }
    }
    protected class PrintableTriangle  {
        protected void print() {
            System.out.println("Dreieck");
        }               
    }
    protected class PrintableSquare {
        protected void print() {
            System.out.println("Quadrat");
        }               
    }
}

The operation can be invoked like this:

Printer printer;
Shape shape;
printer = new Printer();
shape = new Triangle();
printer.invoke(shape);        // prints "Triangle"
shape = new Square();
printer.invoke(shape);        // prints "Square"
printer = new GermanPrinter();
shape = new Triangle();
printer.invoke(shape);        // prints "Dreieck"
shape = new Square();
printer.invoke(shape);        // prints "Quadrat"

Details and Variations

  • Roles will commonly use callout bindings (OTJLD §3) to access properties of their respective bases.
  • If base instances are connected by references to form a graph (tree) roles can use this structure to traverse the base structure while performing individual actions at each node visited.
  • Traversal and other re-usable functionality could be factored out to a super-team of Operation.
  • By nesting all operations inside a bigger team, one more dimension of dispatch can be obtained by each nesting level.
  • The role hierarchy can optionally adjust the given (base) hierarchy by either
    • inserting intermediate roles for more code reuse than the base hierarchy would support or by
    • skipping base classes that are not relevant for the operation at hand.