Anti-OOP Design Patterns
• oop
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.
Singleton Pattern
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 object
instead of 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.
Observer Pattern
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 Button
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
a 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.
Visitor Pattern
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 accept()
as:
Well, this code does not even compile. The statement into for expression tries
to invoke visit()
with CarElement
as argument. Visitor
implements
visit()
for Wheel
, Engine
, Car
…, but not for CarElement
. We used
accept()
function before to let each CarElement
implementation choose the
appropriate 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:
When 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
based on 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
a 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
pattern.
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 Car
:
So accept()
method is not required for every CarElement
but just Car
.
Summary
Long time ago I read a question in Stack Overflow about the difference between Java and Javascript. My favorite answer was:
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.