By Tobias Schlitt, first published at Thu, 16 Oct 2014 07:00:50 +0200
Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.
You can also buy a printed version of the book on Amazon or on epubli.
A while ago I replied to the tweet by @ramsey
Traits are a nice way to provide common functionality to unrelated classes without using static methods on a global Util class.
Which makes them exactly as evil as static access. Funktionality you dispatch to becomes irreplaceable destroying a fundament of OO: Dynamic dispatch.
I want to use this blog post to illustrate the concept of dynamic dispatch which I use a lot recently to motivate creating clean OO structures in my trainings. In my experience, this helps people to understand why we want to write code in this way. After that I will show why traits are bad in this direction.
Dynamic dispatch is actually an implementation detail of object oriented programming languages. To explain it I need to take a little leap back in time and illustrate the code flow in a classic procedural program:
The graphic shows two procedures which call a shared log()
procedure, the arrows visualize the execution flow of the program. As can be seen, a procedural program classically works top down. In order to solve a complex task, a procedure dispatches to other procedures where each fulfills a smaller fraction of the task. It is important to notice that the procedures to be executed are exactly known at compile time of the program (simplified). This is what is called a static dispatch.
Imagine you now want to log all friendship related operations to the new Facelog server, because it generates incredibly useful insights for you. What are your options to implement this change?
You can a) change the log()
procedure itself, i.e. patch it. But this is of course no real option here, because createUser()
would also be affected by this change and most probably many other modules that use log()
. Option b) is to touch the createFriendship()
procedure (and any other friendship related modules) and change them to use another procedure instead of log()
, e.g. facelog()
. Following this approach is viable, but also means quite some work and potential for errors.
In the object oriented (OO) paradigm the situation is different:
The most obvious difference here is that there are two types of errors: Black arrows visualize the actual code flow again, while gray ones indicate object (actually class) dependencies. The UserService
depends on Logger
, FileLogger
extends Logger
and so on.
And this exactly is the crucial point: At compile time, an object oriented program typically only knows about the class dependencies. At this stage it is unclear, how the actual execution flow will be at run-time. It depends upon which particular object is given to the dependant, which might be influenced by configuration, user-input and so on. On a technical level, the programming environment decides out of a set of available method implementations which one is the correct one to use. This is what we call dynamic dispatch.
So how could you perform the required change in this environment? Of course you can just give a different object to FriendshipService
, which provides the API defined by the Logger
type but talks to Facelog instead of writing to a file. The dynamic dispatch will take care. The nice thing: You neither need to touch the FriendshipService
nor do you need to fear undesired side-effects on UserService
and others by touching the FileLogger
.
Now, that is dynamic dispatch: Your execution environment decides at run-time, which method is actually to be called, based on the actual object reference given for a type dependency. There is no need to recompile the program to achieve a change in this dispatching, a different user input or configuration can suffice.
Now take a look at a static method call as shown in the following graphic:
What do you realize? Right, a static method call is actually a procedural call. Indeed, the dispatch is static in this case: There is no way for you to replace this call fine grained, except for the two procedural approaches explained further up. Whenever you introduce a static method call, you throw away the powers that dynamic dispatch unveils to you.
Now look at the next graphic that shows a trait implementation for the Logger
:
Using a trait copies a piece of code into your class, making it a part itself. How would you realize the desired change now? True, there is now way to utilize dynamic dispatch here, but only the procedural approaches work. A trait is static dispatch, too.
Disclaimers:
There is of course much more in the background of this very narrow view on object oriented mechanics, for example subtype polymorphism.
I'm aware that there are concepts in procedural languages to implement a dynamic dispatch, too, for example function pointers. However, for illustration purposes, it is helpful to take these out of scope here.
Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.
Raman Ghadge on Mon, 11 Jun 2018 12:02:35 +0200
Nice post.
Link to comment