Monday, March 13, 2006

Patterns and Programming Languages - Decoupled ?

Almost all literature on software design talk about design patterns being artifacts independent of any particular programming language. Purists hate to associate any pattern with languages and scaffold the essence with terms like "microarchitectures", "design best practices", "three part rule consisting of context, problem and the solution" etc. Heck, the reality of truth is that you (being an implementer) cannot but think of language abstractions whenever you think of a pattern. Someone summarized it so beautifully :

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:

Unknown said...

an interesting thread has started on a similar subject in LtU

http://lambda-the-ultimate.org/node/1360