Purging the Gang of Four

posted on September 13, 2006

I just read an interesting blog post1 someone linked to on ruby-talk:

[Design p]atterns are signs of weakness in programming languages.

When we identify and document one, that should not be the end of the story. Rather, we should have the long-term goal of trying to understand how to improve the language so that the pattern becomes invisible or unnecessary.

The author examines in depth two examples: function calls (a "pattern" in assembly, trivial in higher-level languages) and object-orientation itself (a "pattern" in C, trivial in actually OO languages). He also suggests MVC as implemented in Rails and other agile Web frameworks may be a similar case.

I'm not sure about the MVC-in-Rails one, but his main examples are pretty compelling. Other people have written about how trivial it is to implement some of the canonical GoF patterns in dynamic languages like Ruby. How many of the GoF patterns are actually higher-level design constructs and how many are just language artifacts? Consider this Ruby module2:

module Flyweight
  def new(*args, &block)
    @flyweights__ ||= {}
    args = canonicalize(*args, &block) if respond_to? :canonicalize
    key = Marshal.dump(args).freeze

    return @flyweights__[key] ||= super(*args)
  end
end

Just extend your class with it and your class is automatically an instance of the Flyweight pattern. Magic! ergo, language artifact. I was originally going to say that this doesn't work for the Decorator pattern, but then I realized it does3:

module Decorator
  HANDLED = %w{ object_id __id__ __send__ singleton_method_added }

  def initialize(decoratee)
    @decoratee = decoratee
    @decoratee.methods.each do |mname|
      next if HANDLED.include? mname

      HANDLED << mname
      Decorator.module_eval <<-end_eval
        def #{mname}(*args, &block)
          @decoratee.__send__(:#{mname}, *args, &block)
        end
      end_eval
    end
  end
end

Just include this module in your class to effectively sub-class instance objects. Both non-defined methods and super() will invoke methods in the @decoratee, just as you would expect.

With my first example "failing," I dug through my copy of the GoF book for a pattern I couldn't just implement in Ruby4. Maybe someone will point out that I'm wrong, but it looks like the Composite pattern is such a beast. Sans all the static typing hoops, it seems to boil down to "provide the same interface for all the nodes in a tree, whether or not they are leaves."

This really seems to emphasize the arguments about the design pattern concept's lack of formal foundation. What exactly is a design pattern? It appears that some of them are metaprogramming algorithms. Others are interface conventions. What about MVC? — it doesn't seem to slot easily into either of those two categories.

It might be interesting to go through the GoF book, see how many can be implemented as metaprogramming algorithms, and try to find common threads among the rest.

1 This article is also the fourth time this week I've noticed the blogosphere mention Garrett Rooney. When did you become blogofamous, Garrett?

2 Example code only, no warranty expressed or implied, etc etc.

3 It appears that method_missing doesn't get called for super, which is why I resorted to doing it this way. Any better ideas?

4 "Implementing the pattern" vs. "implementing an instance of the pattern."

Commentary most sage

I got blogofamous? Crap, nobody told me!