Utilize Dynamic Dispatch
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
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 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
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
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.
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.