Since I heard of design patterns for the first time, there was one aspect of it that puzzled me. They seemed to be universally considered a good thing by itself. There seems to be a common belief that the lack of patterns in a program is a bad sign. Or, conversely, the ubiquitous presence of patterns in a software code is a good sign.
I never agreed to anything like that. Ideas like that go against my instincts. More specifically, the instinct to avoid recurrent code patterns (in the dictionary meaning). They are bad for maintenance, to mention one thing. Well, the so called design patterns are exactly that, recurrent code patterns. So, why are they are so popular? The reason is that their basic idea is mostly misinterpreted.
A while back, I read a very enlightening text written by Mark Dominus entitled Design patterns of 1972. He starts by saying:
"Patterns" that are used recurringly in one language may be invisible or trivial in a different language.
And concludes affirming:
Patterns are signs of weakness in programming languages.
I agree with him. And so did most of the people that read and commented about the article. Ralph Johnson (one of the authors of the ”Design Patterns” book) wrote a reply. And Mark wrote a reply to Ralph’s reply. I think there are some ego sparks here and there, but they generally agree with each other. If you are surprised by their agreement, you definitely should read their texts.
Creational Patterns and Dependency Injection
Despite their value, I will not simply quote other people’s opinions. I have a point to make, too. I’ve been reviewing the whole subject recently, and an idea came to my mind. It’s not very innovative, but it’s worth phrasing anyway.
All creational patterns (namely Abstract Factory, Builder, Factory Method, Prototype, and Singleton) are no more than specific applications of dependency injection.
I’ll try to build my case around the singleton pattern. It’s possibly the simplest pattern, and the easiest one to understand. To begin with, I do not want to advocate it as bad pattern. For many simple situations, it fits the problem perfectly. I see it as an extreme simplified solution to a possibly more complex problem. It has drawbacks, though. Some cases require something similar, but just a little bit more sophisticated. I’ll go over some possible modifications to the singleton pattern through the rest of this article.
Before moving on, a comment. I’ll use some C# for my examples below for a single reason, it’s the language I know the most. I’m sure the general idea is valid for any language.
- Global State and Object Life Cycle
Everyone knows that global state is evil. The strongest argument against singletons is that they bring create global to an application. Depending on global state has consequences.
Suppose you have a singleton being used a multi-threaded application. A reasonable assumption is that the singleton object has to be thread-safe, i.e., it has to be able to be accessed by different threads without misbehaving. That’s not completely necessary.
One alternative is having a not-so-singleton object that has one instance per thread instead of one instance per process. This can be easily achieved with thread-local storage, which is supported by most programming languages. Even though these thread-local singletons are not valid to every problem, they are the easiest and safest way to have a thread-safe singleton.
We can go deeper on the idea of multi-instance-singletons (by the way, the term “singleton” might not be appropriate at this point, but that’s just a matter of naming). In many cases, what you really need is one instance per “context” or “call”, or whatever. A nice .NET example is on the System.Web.HttpContext
class. It has a static property called Current
that return an HttpContext
. This might not be commonly seen as an use of the singleton pattern, but it’s a very close variation. The way I see it, it’s basically a singleton that has one instance per http request.
Controlling the life cycle of “singleton” instances in these complex situations can be too complicated, and too specific to still be considered a pattern. That’s not actually a problem. I’ll get back to the subject later.
- Abstraction and Decoupling
Now, I want to expose a different facet of the pattern under discussion. Usually (at least in my code), the exposed type of the singleton instance is more abstract than its actual concrete type. Something like the C# code below.
public static readonly IAbstractThing Instance = new ConcreteThing();
You may reasonably argue that this is not a good usage of OO. If IAbstractThing
were destined to have only one concrete implementation, then you wouldn’t even need the interface to start with. But, in real world applications, that’s commonly not the case.
Exposing an abstract type opens some possibilities. You could, initially, delegate the responsibility of creating the instance to another component. By doing that, you achieve more than one advantage. You are now close to being able to create mock objects for unit testing. A secondary (or should I say primary?) advantage is better decoupling. The component that exposes the instance does not depend on any concrete implementations anymore, i.e., there will be one less dependency around
A group of components that work in this way (one component exposes an abstract object, and another creates the object) has dependency injection in it, even if no DI framework was used.
- Dependency Injection
Of course, the more sophisticated you get, the farther you get from the original pattern. All the intermediary variations are valid solutions for real problems. At each point, the decision of whether improve the code or not is a matter of balancing the costs of development (including test, support, etc.) with the actual advantages it will bring to the application.
The usual shortcut through such evolutionary process is a framework. And there is a lot of stable and dependable DI frameworks free for anyone to use. They are capable of providing a much more powerful solutions than any of the creational design patterns can, and at a lower cost. As I mentioned earlier, life cycle management might get very complex. Not with a good DI framework. Most of them support a broad range of extendable life cycle styles. By the way, my current favorite is Ninject. If you are a .NET programmer, I recommend that you give it a try.
Final Remarks
If you are building a non-trivial application and find yourself using a big mix of abstract factories, builders, prototypes, or whatever, be sure of one thing. You are in need for a good DI framework. Any language that has built-in DI, or has a built-in DI library, or enables such a library to be build, do not need to depend on “creational patterns”.
A similar statement can be made to other types of design patterns. Consider the strategy pattern, for example. It’s trivially implemented using first-class functions (C#’s anonymous methods). The observer pattern can be effortlessly implemented with C# events. The iterator pattern can be graciously implemented with the IEnumerable
interface and some help from the foreach
keyword. Using the yield return
keyword, it’s possible to elegantly represent complex object interactions with a only a few lines of code.
Please, don’t get me wrong. Design patterns are good in many ways. For one thing, they provide a common vocabulary to programmers going through similar problems. What I’m saying is that they are generally misunderstood and overrated. Their ultimate purpose is to become invisible. Do not think design patterns are timeless. They aren’t.
3 comments:
Before, I express my personal opinion about design patterns, I'd like to ask you one question. Why is Ninject you favorite DI framework? I've never used it so, I'd like to hear from you what are the advantages of it in comparission to other frameworks available.
About the design patterns, I'm not a big fan of the common sense concept too. I agree with you they provide a common vocabulary to programmers talk about similar problems. Nevertheless, I see the common sense around design patterns as an attempt to make the software development a kind of factory work which supposedly could be easily done using the right components and design patterns.
In my opinion, this mindset is very limiting. The complexity of real-world applications demands much more from the software developers than using ready-to-go recipes.
Software development is much more a creative work than anything else. I spend most of my development time working with the right side of my brain than the left. In essence, there is no much difference between an artist and programmer.
Possibly, the best comparison between DI Frameworks is here:
http://blog.ashmind.com/index.php/2008/08/19/comparing-net-di-ioc-frameworks-part-1/
http://blog.ashmind.com/index.php/2008/09/08/comparing-net-di-ioc-frameworks-part-2/
Unfortunately, it's from 2008, when Ninject was on version 1. Even then, you can see that it was very featureful. This year, Ninject 2 was officially launched. It was a complete rewrite of version 1, and it got much better. You can read about the new version here: http://kohari.org/2010/02/25/ninject-forever/
Now, I'm not completely sure of the personal reasons why I like it. In fact, I must admit that I haven't used it as much as I wanted too.
I like it because it wasn't meant to be configured through XML (although this can be done). I prefer components that can be primarily configured by code.
I like it because of its extremely elegant use of fluent interfaces, with short and meaningful method names.
I like it because I feel it's an opiniated software (http://gettingreal.37signals.com/ch04_Make_Opinionated_Software.php).
Ultimately, I think I like it because I identify myself with its code style.
Maybe, the greater downside of it is that the documentation of version 2 is not ready yet.
Perhaps I should post something about Ninject in the future...
I almost forgot to comment about your opinion on design patterns.
I agree with you. The comparison with factorywork is insightful. I never liked the idea of standardizing software developement to a point where you simply have to follow a bunch of rules. Much to the contrary, I always liked software development because there are endless ways to make the same program. I have seen this comparison betwen artists and programmers in a book I've read called "Hackers and Painters". Here's an essay about this subject from the same author of the book:
http://www.paulgraham.com/hp.html
And here's the site about the book itself: http://www.paulgraham.com/hackpaint.html
By the way, I think this book has influenced my opinions on a broad array of fields.
Post a Comment