If you develop software with an OOP Language, you need “Double Dispatch”: here’s why (and what the heck it is)
When developing software using Object Oriented Languages — such as Java — there may be various situations in which it is efficient to manipulate some objects according to their more generic interface, and other situations — in the same program — in which it is necessary to access the same objects according to a more specific interface.
Consider for example the case in which, within a certain software architecture, we must manipulate Messages
(eg notifications sent to users of a mobile App or a Web Service): for the part of the architecture that deals with the composition or transport of Messages
it is necessary and sufficient that it is “visible” only a generic description of the Messages
; while for the part of the architecture that deals with the actual sending it is necessary that each different type of Message
(e.g. SMS or Email, or Push notifications for mobile devices) is properly managed.
Below is a (simplified) UML class diagram that describes a system that corresponds to the previous example:
Consider the use case in which a Message
is acquired from a queue and delivered; a possible (simplified) implementation in Java is the following:
The mailBox
object must be able to handle the sending of the message differently depending on whether it is, in fact, an SMSMessage
, or an EmailMessage
.
We can therefore write the following Unit Test (always in Java):
In this test:
- we take a
Message
from a queue — assuming we are given an SMS (via mocking) - we deliver the
Message
through theMailBox
- we check the delivery outcome — which in the case of the test must testify that an SMS has been sent
What is the best possible implementation of the deliver()
method?
The wrong implementation (in Java)
A natural approach is to exploit the so-called “double dispatch”.
The “double dispatch” is a characteristic of certain Object Oriented Languages such that the actual method used at runtime to respond to a certain invocation is chosen not only on the basis of the “characteristics” of the instance whose method is invoked (mailBox
in our example), but also on the basis of the parameters of the method — in the specific meaning of the effective class which they implement in a certain hierarchy of classes (the message
variable in our case).
We could therefore imagine, to have two implementations of deliver(Message)
:
Since however the MailBox
class must be able to be invoked on generic Messages
, we will also have to add a method of the type:
Unfortunately, since Java does not natively support “double dispatch”, this naive approach will not work: mailBox.deliver(message)
will always cause the actual invocation of deliver(Message)
(that is, the method whose parameter has the most “abstract” interface) — which returns null
.
The working implementation — via an “anti-pattern”
A more effective approach would instead consist in the use of a instanceOf
, and of a chain of ifs
:
The test now goes by.
But the quality of the code is not optimal.
Indeed, some “code smells” are easily noticed; if someone will add a new Message
(say a MobilePushNotification
):
- it will not be easy to know that this point exists (or other points) in the code base where it is necessary to add a statement needed to properly handle the new type of
Message
- also when this code will be identified, to add the management of the new
Message
it will be necessary to modify the existing and working code, which in the meantime could have been made more complex by other interventions, and whose modification could therefore determine unexpected problems - a “casting” is applied to a
message
: if the code will be subject to refactoring it is possible that the casting is moved to a position where it will no longer be appropriate
Finally, the good one!
A more efficient approach is instead to implement the “double dispatch” using the Visitor Pattern.
Let’s create a MessageVisitor
:
Then we modify the Messages
so that they accept it:
Finally, we make sure that MailBox
implements MessageVisitor<DeliveryOutcome>
, and we implement deliver(Message)
in the following trivial way:
And a Message
, say an SMSMessage
, will implement accept()
as follows:
This UML diagram summarizes the new design of a MailBox
implementation:
In this way, when we add a new type of Message
, the compiler will inform us immediately that MessageVisitor
needs a new suitable method, and, subsequently, the compiler will always inform us that the implementation of MailBox
also requires the implementation of the new method:
- the knowledge of the system is managed by the compiler — and not by the oral or written memory of the Team which owns the code base
- changes in requirements (e.g. a new
Message
) will be implemented by extending the system (with new methods) without modifying the existing methods
Super neat!
Conclusions
We have seen how the use of “Double Dispatch” is necessary when we have to alternate the use of generic interfaces and specific implementations in a software system coded via OOP.
Furthermore, we have seen how Double Dispatch can be effectively implemented in Java using the Visitor Pattern.
You can find full code (implementations of all cases, with Unit Tests) in my GitHub project repository double-dispatch-java-example.
Other Articles You May like
10 Things Java Programmer should learn in 2020
10 Books Every Programmer Must Read
10 Tips to Improve Your Programming skill
10 Tools Every Software Developer should know
5 Courses to Learn Software Architecture in Depth
20 Libraries and APIS Java Programmer Should Know
Top 10 Programming languages to Learn in 2020
10 Framework and Library Java and Web Developer Should Learn