The common problem with a decorator based on delegation is the lost sense of self. Method calls of the decorated object to itself are not routed through the decorator, therefore remain woefully undecorated.
The Ruby idiom of aliasing and redefining methods, in effect adding around advice, is possible, when the decoration applies to all uses of the “core” class. It is not suitable when you need decorated as well as undecorated variants.
There’s an almost classical: Just derive a subclass from the “core” class and add the decoration in there. Doing this explicitly is tedious, ugly, and multiplies classes semper necessitatem. Luckily, for us and poor, overquoted Father Ockham, Ruby is a highly dynamic language where things are possible that users of other languages can only dream of (longingly or in nightmares, that’s quite a different issue).
So, without further ado, here’s a simple demonstration of the technique.
class Core def m1 puts "Core#m1" end def m2 puts "Core#m2" m3 end def m3 puts "Core#m3" end def m4 puts "Core#m4" end end class Decorator class << self def new(*args) decorate(Core).new(*args) end private def decorate(klass) decorated_class = Class.new(klass) decorated_class.send(:include, Decoration) decorated_class end end module Decoration def m1 puts "Decorator#m1" super end def m2 puts "Decorator#m2" super end def m3 puts "Decorator#m3" super end end end
With that under under our collective belt, here’s the case that motivated my attempt at self-conscious delegation. In a Rails application, I have several layers of
FormBuilders. At the base, derived from
ActionView::Helpers::FormBuilder is a form builder that provides mechanisms. For example for adding labels to input elements and for automatically displaying appropriate widgets for various association types. On top of that, I have two form builders that add layout specifics; one for CSS-based layouts, one for table-based layouts. I still need a further layer for policy: only show users what they are allowed to see and touch. This last policy layer is, of course, independent of layout (and vice versa!), therefore, short of full-fledged AOP, decoration was the way to go.
class DecoFormBuilder class << self def new(object_name, object, template, options, proc) delegate_class = options.delete(:delegate_builder) raise ArgumentError, 'No :delegate_builder given to DecoFormBuilder' unless delegate_class decorate(delegate_class).new(object_name, object, template, options, proc) end private def decorate(klass) decorated = Class.new(klass) decorated.send(:include, Decoration) decorated end end module Decoration def initialize(object_name, object, template, option, proc) super # Do whatever initialization you need here end # Put the decorating methods here and don't forget to # call super. end end