Any good programmer is familiarized with design patterns. Even not-so-good ones ever heard about singleton, strategy, decorator, observer, etc. What is not so common is to find programmers who are aware about the reasons that lead them to use such patterns. For most of us patterns are just a tool. Something that works. And we don’t even try to reason about where the problem they solve comes from. We apply them and continue coding.
But, if you dare to break the confirmism and reason about our good friends the design patterns, you might realise they are there to fight a surprising enemy: the object-oriented programming. Don’t believe me? Let’s analize some popular design patterns from this perspective.
Simple but elegant. We use Singleton pattern to define a class that only can have one instance. In Java:
This code is self-descriptive. By declaring a private method we cannot create
new instances outside this class. The
getInstance() method uses that private
constructor to return the single instance of the class in the whole system.
Right. What’s going on here? What is exactly Singleton solving? In most popular
OOP languages, a class is a factory of objects. As such, you can use it to
create as many objects as you want (or fit into memory). But the real-world
things represented by such instances may be unique by nature. E.g., the class
representing your application in a GUI library. Having more than one instance of
Application would mean your program represents more than one application. And
that’s a restriction of the operating system (one process, one app).
It is very clear that singleton pattern is fighting against the OOP rules. You need a single object in your system, but the language rules prevent that. You are using singleton pattern to cheat the OOP. To break its rules. Singleton is a clear example of a anti-OOP design pattern.
Is there a way to use OOP without cheating with singletons? Of course it is! It’s just a matter of having a language that accepts classes that can have only one instance. Let’s see an example in Scala.
This is really simple and really elegant. In Scala, there is a special case of a
class that only have one instance. It is declared with the keyword
class. In this example, the identifier
Application may refer to
both the class and the instance. Thus the class is not an object factory any
longer. We got our single instance type without cheating the language.
Another really popular pattern. Observer pattern is used to notify state changes on an object to other objects. Some example:
Let’s say we have a GUI library with a
Button class that represents a button
UI control. The
OnMouseClick interface represents an action that would be
executed when a mouse click event is detected on a given control. The
class provides a
setOnClick() method to add one action to be executed when
mouse click is detected. What
Button does in
addOnClick() is not shown, but
you can assume it stores the
onClick object in a private variable and will
invoke it when a mouse click is detected.
In order to observe the button, we must implement
OnMouseClick with our custom
action. Typically using a anonymous Java class:
It’s time to think about why do we need this pattern. What we are doing here
is represent pure behavior using the
OnMouseClick class hierarchy. As we have
seen in the example, we used an anonymous function that holds no internal state
to do its job. Do we really need to declare an interface for that? And do we
need to implement classes (anonymous or not) to define what to do in case of
a mouse click event?
If we think about pure OOP, the answer is yes. Classes are the mechanism to represent behavior, and interfaces the way to deal with polymorphism. It may sound weird to use an interface to represent a single behavior. So it does using a class to implement it. Thus, it seems like we are fighting against OOP. We need a mechanism we lack to represent a single action, and we twist the OOP tools to represent something similar to that.
There is a much better abstraction to represent single actions in the system. As Gandalf would say: use functions fools!
In this Scala example, the observer is not represented by an interface but a
function. We define
OnMouseClick as an alias of any function that receives
Control instance as argument and returns no value (
Unit). Since Scala
functions are first-class citizens, you can pass them as argument to other
functions, assign them to variables, etc.
As simple as that. Thus, observer pattern provides the means to simulate functions using objects due to the lack of the former in pure OOP. This pattern, and others that try to encapsulate pure behavior using objects (Strategy, Factory, Adapter…) are actually anti-OOP patterns.
Our third anti-OOP pattern is not as obvious as the previous ones. Mainly because Visitor pattern is not even easy to understand. In general terms, Visitor is used to execute an algorithm over an unknown object structure. Let’s see the following example:
Let’s start discussing these two interfaces.
CarElementVisitor represents the
entity that visits all the parts of a car. Here visit means what the actual
implementation decides. One
visit() method is provided for each element that
can be visited.
On the other hand,
CarElement represents part of a car structure that will be
visited, including the car itself. The
accept() function must accept a visitor
that will be instructed how to visit the structure. We can implement this
interface as follows:
For each single element,
accept() only invokes
visit() on the visitor. For
Car class, it requests each subelement to accept the visitor before letting
it visit itself.
Finally, we could provide an implementation for a
CarVisitor, as in:
Why do we need this so complicated design? Why don’t we implement
Well, this code does not even compile. The statement into for expression tries
CarElement as argument.
Car…, but not for
CarElement. We used
accept() function before to let each
CarElement implementation choose the
visit() implementation that works for it:
Perhaps you never heard about it, but this have a name. What we are simulating here is known as double dispatch, or more general multiple dispatch. You are familiarized with single dynamic dispatch. The actual implementation of a method is selected in runtime by the target object:
hello() is invoked, the actual implementation is selected in base of
this object (
Greetings implementation). That’s single dynamic dispatch.
What’s double dispatch then? The ability to choose the method implementation
this object and also one of the arguments passed to the function.
That’s what the non-compiling example above was trying to show. If Java would
support double dispatch, it would be possible to invoke
visit() by passing
CarElement object, and the runtime would select the appropriate
implementation depending on the instance passed as argument.
Double dispatch is rarely found in mainstream OOP languages (C# has support
for that using
dynamic type). One more time, the Visitor pattern is a way to
simulate the lack of support for double dispatch. It is another anti-OOP design
Just to mention, a more elegant way to simulate multiple dispatch is a language feature known as pattern matching. Again, Scala is a good candidate to show an example.
Now it is possible to have our double dispatch in
accept() method is not required for every
CarElement but just
One is essentially a toy, designed for writing small pieces of code, and traditionally used and abused by inexperienced programmers.
The other is a scripting language for web browsers.
Compared to other programming languages, Java is a toy. And it is not a coincidence that Java is the favorite language of OOP lovers. OOP is simple as well. But insufficient in some cases. It is highly recommended to know the weakness of every tool we use in our profession. And behind the so often acclaimed OOP design patterns there is a dark side. Does it mean we should avoid OOP? Of course not! It just means OOP might be insufficient. Use design patterns to mitigate the effects of its lack of power. But be aware of that. And, of course, leave your comfort zone and explore other paradigms. Your coding skills will thank you.