Subscribe to
Posts
Comments

Self-conscious Decoration

Variations on the Decorator Design Pattern in Ruby have already been discussed in several places. For my special purpose, none of these approaches works.

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

9 Responses to “Self-conscious Decoration”

  1. on 13 Feb 2007 at 05:37marcello

    Hello Michael, it’s funny how at the same time both of us faced the problem of handling large record sets with active record, and focused on creating a decorator that had a way to be a “complete” and “invisible” shell to the decorated object.

    But, thanks to the expressiveness of Ruby, we came to different implementations: I used a blank class, an invisible slate that wraps the original class. http://pastie.caboo.se/39504

    The first drawback is that Kernel#send will invalidate any protected/private access modifier, but send will behave like a method call in 1.9 so why should backport it to 1.8? :) )

    Hope that you enjoy it!

    marcello

  2. on 13 Feb 2007 at 11:19michael

    Marcello, if I’m not mistaken your approach does not solve my fundamental problem with delegation, i.e., that self refers to the “wrong” (inner) object and so methods called from there on itself are not decorated.

  3. on 13 Feb 2007 at 18:32marcello

    michael, you’re right. but I don’t think there’s an agile way of implementing this kind of decoration, or maybe this could be a starting point (untested and written here)

    require ‘active_support’
    module Deco
    def self.included(base)
    blank = Proc.new {}
    base.instance_methods.each { |m|
    alias_method_chain m, :with_decoration
    define_method(“#{m}_with_decoration”, &blank)
    }
    end
    end

    module MyDecoration
    def old_method_with_decoration
    # do something
    old_method_without_decoration
    end
    end

    klass.extend Deco
    klass.extend MyDecoration

    but surely there are better ways..

  4. on 13 Feb 2007 at 22:41michael

    Marcello, the approach I demonstrate in the article already handles this. That was the entire point. Try the code and you’ll see it.

  5. on 20 Feb 2008 at 08:24Don

    Michael, I like what you’ve done here. To help me understand better including the Decorator Pattern, as an exercise I modified your Decorator class so I could subclass it and be DRY with the new and private decorate methods. I wonder if you could comment on what I’ve done.

    The idea is that I could have multiple ‘Decorators’ each with its own set of methods but make use of the generic aspects of the Decorator itself (this is just a minor addition and doesn’t have much to do with the concept you are showing but might be useful to others). Decorator can be used generically to assist in wrapping any class.

    I show only my ‘SpecialDecorator’. I also show these separated into individual files.

    deco_core.rb (your Core equiv with just 2 methods)
    class DecoCore

    def m1(message)
    puts “DecoCore#m1 ” + message
    end

    def m2(message)
    puts “DecoCore#m2 ” + message
    end

    end

    decorator.rb (your Decorator modified to be a superclass)
    class Decorator
    class

  6. on 20 Feb 2008 at 08:28Don

    My comment was too long…here’s some more:
    decorator.rb
    class Decorator
    class

  7. on 20 Feb 2008 at 09:33michael

    Don, I have to admit that I don’t quite understand what you’re trying to do.

  8. on 20 Feb 2008 at 13:49Don

    Michael, unfortunately my comment was too long and was cut short. If you’d like I can email my code to you. I’d really appreciate your comments. If it has value you are welcome to use it to add to your posting.

    In short, I want to have a Decorator class that I can reuse to do the work of wrapping other classes. Then I want Decorator subclasses that provide the methods (what you send to the decorated class). They make use of the class being wrapped as well as provide their own unique logic. I think that’s the idea of the Decorator Pattern. These are in separate files because I might have lots of them.

    Beyond that I was just experimenting with your ideas and getting a similar example working that incorporated my ideas. I wanted to know if what I was doing made sense to you and if you could give me some pointers to help clarify my understanding of Decorators and what you are trying to accomplish.

    Email me if you’d like the code I have.

    Thanks Don

  9. on 20 Feb 2008 at 14:54michael

    Don, I see your point. However, instead of a homebrew solution that implements part of aspect-oriented programming, you might want to have a look at a full solution such as Aquarium.

Leave a Reply

Fork me on GitHub