With the ascension over the last half-decade of Ruby into the upper echelon of programming languages for business applications, there has been ever-increasing demand for Ruby developers. Which means there are a lot of folks who cut their OO teeth on languages like Java and C# who now find themselves swimming in Ruby’s somewhat unfamiliar, crimson-tinted waters.
Those of us who have been working in strongly-typed OO languages for a very long time – since, say, the GOF Design Patterns book was first published – learned not only OO concepts and the syntactic idiosyncrasies of our language, but also design patterns – commonly-accepted ways of solving recurring problems in our applications.
If you’ve been with Java over the years – say, from Java 3 to 4 to 5, you’ve seen the language evolve to include features formerly relegated to the domain of the design pattern. Java’s for-each loop is one such example – once that became a language feature, it was suddenly less important to know about the more generalized Iterator pattern.
Enter Ruby (and dynamic languages in general). It turns out that many of the design patterns in that grand old Gang of Four book were situational – they apply generally to strongly-typed languages. If there is one “metapattern” that describes most of the original design patterns, it is this: “Use composition rather than inheritance in order to introduce runtime flexibility into your designs.” Quite simple: object composition – relationships between objects – can be changed at runtime in a strongly-typed language like Java. Inheritance – the only other way of introducing externally-defined capabilities to a class – cannot.
In Ruby, the very nature of the language makes much of that line of thinking obsolete. Through its dynamic nature, Ruby classes can be modified programmatically, at runtime. This statement often scares the daylights out of a Java developer when it hits them for the first time. Upon closer inspection, though, this language feature can be used to great effect. Behavior and associated information can be ascribed to specific instances of classes based on the situation in which they find themselves. Imagine a Person object who happens to walk into a busy bank and gets into the back of a queue. In a Ruby system describing this Person, we could blend in methods like start_waiting and elapsed_wait_time? – and those methods could then be discarded later when the Person finishes its transaction and leaves the bank.
How might we have done this in Java? After all, situationally-appropriate behavior is not unique to Ruby – it’s a generalized concept that could be expressed in any language. In Java, where class behavior is fixed at compile time, we use a specialized form of composition. The Gang of Four called it the Decorator pattern. So we have a situation where an inherent language feature of one language (Ruby) completely obliterates the need for a design pattern that helped legions of OO problem-solvers in earlier languages.
It turns out that the evolution away from patterns toward language features is something that has been noted by many others. Suggested reading for those interested: