The dirty unacknowledged secret of design patterns is that they’re strongly coupled to a language. For example, switching from a statically typed language like Java to a dynamic one with forwarding substantially changes the approach to Factory, Proxy, Singleton, Observer and others. In fact, they’re often rendered trivial.
For me, after being indoctrinated in programming with classes in C++ and Java, when I start modeling the solution of a problem in Ruby, I have to step back and ruminate on the changed level of implementation for almost every pattern. After all, I don't want to make my Ruby code Java-ish by defining a base class for the Strategy Design Pattern. Ruby has made Strategy invisible within the implementation of Blocks and Closures - as an implementor you need to think of these abstractions when you smell of a Strategy in your model.
Can we ever decouple patterns from the language of implementation ? In my last post on software abstraction, I had discussed the implementation of Ruby Iterators as control abstractions offered by the language. For clarity, let us look at the implementation once again:
def producer
100.times{|i| yield i if i%5 == 0}
end
def consumer
sum = 0
producer {|v| sum = sum + v}
print sum
end
The above snippet is an elegant implementation of the producer consumer model which combines the Iterator pattern (through the internal iterator
times
) and the Command pattern (through Closure). This composition is only possible in languages which offer the invisibility of these two design patterns through language features. In typical object-oriented languages like C++/Java, we think about classes first (though the name is OO), all our abstractions are based on designing effective class hierarchies and delegation. In dynamic langages, it's always objects-first - types or classes are objects at runtime (not only at compile time). Types / classes can be manipulated at runtime, methods added to supplement behaviour, thereby obviating the need for many creational patterns like Abstract Factory, Factory Method, Flyweight, State, Proxy etc. Types serve as Factories !!
Dynamic languages support higher order functions - functions as first class objects along with closures (behaviors along with attached lexical scope) and continuations (a lexical scope along with a control chain). If Ruby is my implementation language, I will implement Strategy, Command, Template Method and Visitor using higher order functions. If I use C#, then I will go for Delegates and Anonymous Methods.
The following example of Strategy in Ruby is from RubyGarden and uses Closures :
class Context
attr_accessor :strategy
def do(*args)
strategy.call(*args)
end
end
ctx = Context.new
ctx.strategy = proc { do stuff }
ctx.do(some, args)
Over the last few years I have been asking myself - is it possible to think of design patterns without an implementation language ? The GOF pattern template has a section named "Implementation", which discusses all implementation related issues of the pattern (including the language of implementation). Next time when you switch the implementation of your Iterator Pattern from C++ to a coroutine based one of Ruby, check out if you need to make any changes to the "Participants" and "Collaboration" sections. Drop me a line if you don't need to ..
1 comment:
an interesting thread has started on a similar subject in LtU
http://lambda-the-ultimate.org/node/1360
Post a Comment