I finally got my hands on a shiny new copy of Design Patterns in Ruby1. The book itself is not brand new and it was already widely praised by many different people online, so I wanted to take a look for myself.
To my surprise, the book is a hardcover edition, which makes it look more professional and more durable than the average programming book2. It’s also smaller and shorter than the average programming book2 (340 pages), which makes it much easier to carry around and less intimidating to read. It’s also not meant to be a reference book, so it is actually pleasant an easy to read all in one go, as you’ll soon find out.
What is it about? — well, design patters in the Ruby language of course. But it’s not the usual brainwash of programming theory you would expect by a typical book on patters, it has plenty of examples of real code. When I say real code I don’t mean the usual Dog/Cat/Horse/<insert animal here> classes or juke-box simulations which don’t work at all etc. etc., I mean actual snippets from well known Ruby applications, like RubyGems, FXRuby and, of course, Rails.
OK well, there’s an exception perhaps: Russ did include a few wild life simulations (ponds with frogs and similar), but it’s only for your own good, and for the sake of tradition.
Anyhow, let’s start from the beginning…
Part I: Patters and Ruby
The first part of the book serves as a general introduction to the other two parts. If you know the basics of both design patterns and Ruby, you can safely skip this as you won’t find anything of overwhelming interest here.
Personally I really liked Chapter 1 though, “Building better Programs with Patterns”, in which Russ does a great job in summarizing the original GoF book3 into four points:
- Separate our the things that change from those that stay the same.
- Program to an interface, not an implementation.
- Prefer composition over inheritance.
- Delegate, delegate, delegate.
Also, although it does not come from the Design Patterns book but from building real systems, the author adds the YAGNI (You Ain’t Gonna Need It) principle4 as a reminder to resist the temptation of implementing things which may be needed later on, even if they are not needed right now.
The chapter ends with an outline of the patterns which will be presented throughout the book: 14 out of the original 23 patterns by the Gand of Four will be discussed in Part II and 3 bonus “Ruby-only” patterns will be examined in Part III, as a special treat.
Chapter 2 (Getting started with Ruby) feels perhaps a bit out of place. As others pointed out5, why does a book on advanced Ruby programming techniques include a 35-page-long introduction on the Ruby language? The answer was given by Russ himself in an interview6:
“The reason that I included the introductory chapter about Ruby in there was to make the book accessible to folks with little or no Ruby background.
Now honestly, I don’t think that you could come to my book with no background in Ruby and walk away from it an expert Ruby programmer — it’s not really that kind of introductory book.
But I do think that someone with experience in other languages could read my book and come away knowing about Ruby, understanding what all the shouting is about.”
I admit, I skipped this chapter during my first reading because I was eager to move on to the main part of the book, but I did read it afterwards (I had to write this review after all!). It’s quite a nice introduction aimed at the average .NET/Java developer: Russ provides a step-by-step presentation of the main features of the language while holding the reader by hand when something weird or scary comes about:
The slightly strange-looking syntax in this code is actually a tip-off something deep and important: In Ruby, everythng — and I mean everything — is an object.
Of course Chapter 2 won’t turn you into a Ruby guru, but it definitely fulfills one of the author’s goals: bringing developers of other languages closer to Ruby, and give them a tiny taste of how Ruby can be wickedly powerful.
Part II: Patterns in Ruby
Part II constitutes the bulk of the book, describing 14 GoF patterns in 220 pages. The patterns covered are the following:
- Template Method
- Factory Method
- Abstract Factory Method
Why not covering all 23? Well, because to be honest, they are rarely used in Ruby. Furthermore, in some cases some of the ones examined in the book may feel a bit unnatural to the average Rubyist: how many times did you ever think about using an External Iterator when
each is normally available as default internal iterator for any Array-like class?
Each chapter in this part is devoted to a particular pattern and it is organized in more or less the same way, as outlined in the following sections.
Introduction and Personal Anecdotes
Most chapters start with a personal anecdote involving the author: it may be a memory related to his first job at the local grocery store (Chapter 8), or about the day he decided to buy his son a bike (Chapter 14):
“I remember the day we bought my son his first bike.” […] I spent hours trying to pull together a minor junkiard of parts according to instructions that would have baffled the entire National Security Agency. As it turned out, picking the bike was the easy part: putting it together was the real challenge.
This was used to introduce the Builder pattern, and how to use it to configure objects which include different logical parts.
Personally I find this technique particularly useful to introduce a particular problem from a different, more mundane prospective instead of starting off with an abstract theorethical description of the pattern itself.
The anecdote is then followed by the description of the actual programming problem for which the specific pattern will be used.
Description of the Pattern and Initial Implementation
An initial implementation of the pattern in Ruby will be provided more or less immediately after the introduction of each chapter, often accompanied by a simple UML diagram.
This implementation normally has quite a few conceptual flaws, which are then examined and corrected step-by-step the chapter to obtain a more “Ruby-friendly” solution.
A More Rubyfied Version of the Pattern
The final implementation of each pattern is often very different from the initial attempt, and it may contain quite a lot of Ruby-specific code. The author does an excellent job in suggesting pattern implementations which often use blocks,
Proc objects or method redefinitions when needed, to make the code more succint and more readable at the same time, as all Ruby code should be.
By doing so, even people who are still learning Ruby will understand how to use some very useful Ruby idioms which can be a bit difficult to grasp otherwise.
Using and Abusing <Pattern>
Patterns are often overused and misused, and some people normally end up wondering if they should be used at all, after all. This section (present as a matter of fact in every chapter of part II an III) examines the pitfalls of the pattern and the most common mistakes developer make when applying it.
It is by far the most useful section of each chapter, and that’s what I’ll be reading and re-reading every time I’m thinking about using a particular pattern in my code. As a matter of fact, these sections make you realize that every pattern has its own inherent flaws and dangers, and that it is far from being a Silver Bullet. Even when you’re supposed to use a pattern to accomplish something, be aware that something nasty can happen unless you’re extra careful: this, perhaps, is the true Golden Rule conveyed throughout the whole book.
<Pattern>s in the Wild
This is another very interesting section which is included in every chapter of part II and III. After describing what a pattern does, how it can be used and how it should be used, you’ll finally find some interesting examples taken from real world applications.
By “real world application” I mean something like ActiveRecord7 (Observer, Command, Adapter, …), DRb8 (Proxy) or FXRuby9 (Composite), for example, i.e. important programs and libraries which are used in production environments.
Personally, I was really glad to find such examples in this book: it definitely helps you feeling design patterns as something more practical and useful than pure software architecture theories.
Wrapping it Up
“Wrapping it Up” is the title of the last section of each chapter of Part II and III. It’s basically a summary of the whole chapter and thus a useful way to recap the most important concepts. I found this section particularly useful when using the book as a design pattern reference, after reading it for the first time: this section provides a quick and essential overview of each pattern — and the most important DOs and DON’Ts, too.
Part III: Patterns for Ruby
By the time you get to Part III you’ll definitely feel that Ruby can do more_. Some of the Ruby implementation of certain patterns described in the book make extensive use of blocks and Proc objects, and the @methodmissing@ method (although potentially dangerous unless extra care is taken) gives us a more immediate way to obtain delegation, for example when creating Proxies.
Also the fact that objects can be modified at runtime by adding and removing methods “as needed” seems quite an underused feature in traditional patterns, simply because those patterns were first conceived for languages which are very different from Ruby and are perhaps less liberal than Ruby when it comes to dynamic features10.
These particular Ruby features can be used (and abused, of course) to implement more Ruby-esque patterns, such as the ones included in this part of the book:
- Internal Domain-Specific Languages
- Convention Over Configuration
These are just examples, of course some may complain because the Active Record or ORM pattern are missing, but this is understandable as it may be considered too specific compared to the others.
Each pattern is examined in detail, and I particularly like way the DSL pattern was described: Chapter 16 explains how to develop a simple but effective Ruby DSL from scratch for creating file backups. This can be particularly useful for people who never tried creating DSLs before, but also for developers who tried, but want to improve their skills.
Chapter 18 (Convention Over Configuration) is sufficiently clear and detailed, perhaps even too much if you already know how Rails was developed (and all the hype which follwed).
On the other hand, I was a bit disappointed by Chapter 17 (Meta-Programming). Maybe it’s because I built up extremely high expectations about it while reading the rest of the book, but it just felt too short and not detailed enough for my liking. If I had to write such a chapter (which would have been actually very hard), I would have started from an excellent post by Ola Bini11 which introduces eleven meta-programming techniques, and built up content and examples from there. The only reason why — I think — Russ didn’t do it in his book was length/balance constraint: a properly detailed chapter about meta-programming in Ruby could easily take up over forty pages!
As I said in the beginning: this is not meant to be a complete, in-depth, reference book on everything you may want to know about design patterns in Ruby. That’s why, as a matter of fact, you can actually read this book all the way through without getting utterly bored. Russ uses an informal, yet appropriate style to turn potentially complex, theorethical computer science principles into easy-to-understand, useful tools which can truly improve the way you code.
The whole book flows very very nicely. I actually recommend reading this book in sequence, without skipping chapters, because each pattern is described in a way that is somehow linked to the following ones, so that you can understand and learn about the pros and cons of each one in a more natural and useful way.
OK, I would have loved to see Part III as long as Part II, probably, but overall I’m very, very satisfied of what the book taught me. The only problem is that it also made me suddenly realize all the naive design mistakes I’ve been making when coding in Ruby, so I’ll now feel compelled to fix at least some of them…
Definitely a worthwhile read, I just hope to see more books like this, or even a second edition of this one soon!
2 Think of Programming Ruby: The Pragmatic Programmer’s Guide, 2nd Ed. by Dave Thomas with Chad Fowler and Andy Hunt, Pragmatic Programmers, 2004.
3 Design Patterns: Elements of Reusable Object-Oriented Software, by By Erich Gamma, Richard Helm, Ralph Johnson, John M. Vlissides (a.k.a. the Gang of Four), Addison Wesley Professional, 1994.
10 This can be a good or bad thing depending on the way you look at it, and what you want to use the language for. The fact that Ruby is dynamically typed makes it easier to do things which are totally impossible in C++ or Java, but it also introduces a whole new set of potential dangers.