From: elbows@... (Nathan Weston) Date: 2003-12-04T07:17:07+09:00 Subject: Using Modules as Decorators Someone recently mentioned the idea of using Modules to implement the Decorator pattern (though I can't find the post now, so maybe I imagined it). It got me thinking and today I sat down to see if I could do it. I ended up with a module Decorator,that can be included into another module to turn it into a decorator. You can then extend an object with the decorator to decorate it. The next part is that methods defined in the decorator can use a call_orig method to call to original method that they are replacing. For example: module Test include Decorator def foo print "Test#foo\n" Test.call_orig(self, :foo) end end class Foo def foo print "Foo#foo\n" end end f = Foo.new f.extend(Test) f.foo prints: Test#foo Foo#foo It also supports multiple layers of decorators: ie f.extend(Deco1) f.extend(Deco2) and call_orig will still do the right thing. This approach avoids 3 problems with using delegates to implement decorator: Delegates can't be marshalled without some extra work Delegates can confuse == When using delegates, you have to be careful about passing around references to self, as you might end up with the inner, undecorated object, where you wanted a decorator. And here's the code, so you can all find my mistakes: module Decorator def Decorator.append_features(mod) super #Add these as module (not instance) methods, #to the module we are extending. #We can't just define them as Decorator.*, because they won't be #inherited in the way we want. class << mod def orig_name(name) return "#{name}_#{self.name}" end #Create an alias to one of obj's methods, so that #we can wrap it with a decorator method def decorate(obj, name) #We have to eval string to avoid scope issues: #class << obj # alias_method(:orig_name(name), :name) <-- These variables # are substituted through string expansion #end eval("class << obj\nalias_method(:#{orig_name(name)}, :#{name})\nend") end def extend_object(obj) #If the extended object has any methods that we are #going to redefine, make aliases to the originals #so we can still call them. instance_methods(false).each { |m| if obj.respond_to?(m) decorate(obj, m) end } super end #Call the original definition of name on object def call_orig(obj, name, *args) obj.send(orig_name(name), *args) end end end end