Review of two Design Patterns books
published: Tue, 27-Jun-2006 | updated: Tue, 27-Jun-2006
I've just finished a couple of Design Pattern-related books, both of which are good in their respective and very different ways. The first is Holub on Patterns by Allen Holub, and the second Head First Design Patterns by Eric & Elisabeth Freeman.
Although both books are good and cover their topic well, I much preferred Holub's, mainly for his unflinching approach to writing well-designed and crafted code. And, I must admit, that's an approach that many people find annoying.
Holub starts his book on Design Patterns by talking about what patterns are and why they're important but quickly starts talking about good design. He's right: you can't begin to grasp why design patterns are important until you can easily verbalize what is good design and why it's good. So he talks about object- orientation and what an object should be designed as: a bundle of behaviors. To that end, he dismisses getters and setters as bad (well, actually, he uses the word "evil"). You can see my take on this here.
(Note: I'm doing some research into immutable objects in multithreaded scenarios -- that is, setters are bad when you share an object amongst many threads -- and I'll be talking about my thoughts on this later.)
Just after he biffs you with getters and setters being bad, he socks you with an extremely reasoned argument to why sometimes implementation inheritance is bad too and that you should think about interface inheritance instead. The argument is persuasive: he uses a simple stack to illustrate the Fragile Base Class problem where, no matter how hard you try, you can expose information about a base class' implementation that descendants will take advantage of and thereby strongly couple your subclasses to your superclasses. And with that he dismisses the Template Method.
Note though, lest you get the wrong idea, these are not presented as hard and fast rules; Holub merely points out the problems and points out that avoiding them is relatively simple, but your mileage may vary.
After those introductory chapters, he delves into two totally separate non- trivial applications: the Game of Life, and an embedded SQL implementation. These two applications are real-world and hefty enough to show you the issues and problems that Design Patterns are meant to solve and how to solve them. The Game of Life one is interesting to Java developers especially since it talks an awful lot about the issues you face in putting a Swing GUI on top of an application that's already well-refactored. The embedded SQL one is interesting too in that it produces a pretty complete SQL-driven database engine resident in memory (this is great for testing purposes, for example).
Throughout these two applications, he discusses which patterns are valid for particular scenarios and which aren't. He discusses the problems that you have to solve, how to recognize opportunities for refactoring to patterns, and how to implement them. As previously mentioned, all the code is in Java, but it's not that hard to read or that different from C#.
Head First Design Patterns is pretty good. It uses pictures, goofy interviews, exercises, puzzles, etc, to help teach you Design Patterns. In general, the authors succeed pretty well, although I must admit that I started to skip the puzzles and exercises after a couple of chapters. I also started to skip the tedious "interviews" with the individual patterns as well ("So, Singleton, why are you special?" "Because there's only one of me" kind of thing). I don't know whether this was due to my knowledge of the subject matter and a beginner might get more out of them.
Overall they do a good job. The example "projects" are well chosen for a fun factor as well as to illustrate the current pattern under investigation. So we have the Duck simulation game ("Rubber ducks don't fly, therefore we should use the Strategy pattern to implement flying behavior"), the pizza store, the remote control that can control many things, and so on.
Problem was, although some patterns were well covered, some had holes. The one I can think of straightaway is the Singleton pattern. It's pretty well covered but mostly from a fairly basic viewpoint. So there's no talk about how you test a singleton (amazingly, for other patterns they talk about testing, but not for dear old Singleton), how you test code that uses a Singleton, what the (now) preferred way is to implement a thread-safe one in Java. And I must admit, Singleton is pretty much one of the easiest patterns to implement, so this was disappointing.
I must also say that the reason I bought Head First Design Patterns was that it was recommended by someone who used to work with me. He said he understood it all now having read the book cover to cover and the novel way the subject matter was presented had helped enormously. I'd tried hard to teach him certain patterns, but it wasn't helping his designs or code, so I was hoping for good things. Unfortunately the latest code review of some of his design and code showed that nothing had really stuck, so I wonder whether the novel approach really helps learning or whether it's just a fun read.
So overall I'd give Holub on Patterns an 8 out of 10, and Head First Design Patterns a 7.
(Aside on Singletons...
Following the recommendation in Design Patterns by the GoF, people usually implement Singletons using a lazy load pattern like this (C# code):
public class SingularThing { private static SingularThing instance; private SingularThing() { } public static SingularThing Instance { get { if (instance == null) instance = new SingularThing(); return instance; } } // other stuff... }
That is, declare a singleton class and make its constructor private (so that other code cannot call it), declare a static field to hold the one and only instance of the singleton class, and then declare a readonly property to read the instance. In this getter, if the instance has not been created yet, go ahead and create it.
Simple enough, but it's broken in a multithreaded environment since many threads could test the one and only instance to be null at the same time and all try and create a new one.
So the next step is to make it explicitly thread-safe:
public class SingularThing { private static SingularThing instance; private static object padlock = new object(); private SingularThing() { } public static SingularThing Instance { get { lock (padlock) { if (instance = null) instance = new SingularThing(); return instance; } } } // other stuff... }
That is, we surround the initialization and returning of the
instance
in a lock
statement, and make the actual
field volatile
. This is guaranteed to work properly but suffers
from a small performance problem: the lock is used all the time. In essence we
would like the lock to only be used when the instance needs to be created but
never after that (since reading an object won't incur any race conditions).
So the next step is this optimization:
public class SingularThing { private volatile static SingularThing instance; private static object padlock = new object(); private SingularThing() { } public static SingularThing Instance { get { if (instance == null) lock (padlock) { if (instance = null) instance = new SingularThing(); } return instance; } } // other stuff... }
This pattern is known as the double-checked locking pattern. Notice
that if we see instance
as null, we place a lock, and then recheck
instance
to be null and only if it is do we actually create the
singleton instance. This avoids the problem of many threads all trying to
initialize the same object at the same time. We have to mark the
instance
field volatile
to force the thread to read
the value from memory rather than the cache. This, of course, means we have a
small but measurable performance hit every time we access the field.
However, this code can suffer from a non-obvious bug. In C#, we're lucky: the memory model for .NET ensures that this double-checked locking works as it should. In Java though, we could be in for a rude awakening. In Java 1.4 or earlier, marking a field as volatile doesn't automatically put up a memory barrier and we can find that writes to non-volatile fields can seem to occur after a write to a volatile field. In other words, the initialization of other fields in the singleton can seem to occur after the instance field is set. You could get a partially initialized object. Brrr. In Java 1.5, the memory model has been strengthened so this double-checked locking will work. This is where Head First Design Patterns stops its discussion of Singleton: "beware if you're not using Java 1.5."
However, why bother worrying about whether it will work or not. The better solution is this one:
public class SingularThing { private SingularThing() { } private static class InstanceCreator { internal readonly static SingularThing instance = new SingularThing(); static InstanceCreator() { } } public static SingularThing Instance { get { return InstanceCreator.instance; } } // other stuff... }
Here we introduce a nested (or inner) class into the singleton class and
depend on the guarantees provided by the run-time to create a class type once
and once only when the class is first accessed. In other words, the first time
we access SingularThing.Instance
, the run-time will create the
InstanceCreator
type and will initialize it (which creates the
instance we need). The run-time guarantees that this process can only be
executed once and also that all threads will see the initialized type once
created.
Notice something else: we don't have to declare instance
as
volatile so we avoid that minor performance hit every time the
instance
field is accessed.)