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

Hi,

  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 decorate(obj, name)
      orig = orig_name(name)
      (class << obj; self; end).class_eval {alias_method(orig, name)}
    end
  #Call the original definition of name on object
  def call_orig(obj, name, *args)

obj.send(orig_name(name), *args)
end
def call_orig(obj, name, *args, &block)
obj.send(orig_name(name), *args, &block)
end

···

At Thu, 4 Dec 2003 07:17:07 +0900, Nathan Weston wrote:


Nobu Nakada

Nathan Weston wrote:

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).

That was me.

However, this still doesn’t solve my problem - you see, I need to also
be able to remove functionality from each object in addition to adding
it, and I don’t think you can remove a module from an object once its
been added.

Perhaps a new feature in Ruby 2 perchance?

···

> (class << obj; self; end).class_eval {alias_method(orig, name)}

that is too cool

-a

···

On Thu, 4 Dec 2003 nobu.nokada@softhome.net wrote:

ATTN: please update your address books with address below!

===============================================================================

EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
PHONE :: 303.497.6469
ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
STP :: Solar-Terrestrial Physics Data | NCEI
NGDC :: http://www.ngdc.noaa.gov/
NESDIS :: http://www.nesdis.noaa.gov/
NOAA :: http://www.noaa.gov/
US DOC :: http://www.commerce.gov/

The difference between art and science is that science is what we
understand well enough to explain to a computer.
Art is everything else.
– Donald Knuth, “Discover”

/bin/sh -c ‘for l in ruby perl;do $l -e “print "\x3a\x2d\x29\x0a"”;done’
===============================================================================

nobu.nokada@softhome.net wrote in message news:200312040501.hB451XUm013568@sharui.nakada.kanuma.tochigi.jp

Hi,

  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 decorate(obj, name)
      orig = orig_name(name)
      (class << obj; self; end).class_eval {alias_method(orig, name)}
    end

Cool! I was wondering if there was a syntax for that, but I couldn’t
figure it out so I just gave up and used eval.

  #Call the original definition of name on object
  def call_orig(obj, name, *args)
obj.send(orig_name(name), *args)
  end
    def call_orig(obj, name, *args, &block)
      obj.__send__(orig_name(name), *args, &block)
    end

What does send do? And what’s the block for?

Nathan

···

At Thu, 4 Dec 2003 07:17:07 +0900, > Nathan Weston wrote:

Asfand Yar Qazi <im_not_giving_it_here@i_hate_spam.com> wrote in message news:bqmkd5$eng$1@news8.svr.pol.co.uk

Nathan Weston wrote:

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).

That was me.

However, this still doesn’t solve my problem - you see, I need to also
be able to remove functionality from each object in addition to adding
it, and I don’t think you can remove a module from an object once its
been added.

Perhaps a new feature in Ruby 2 perchance?

Yeah, not being able to undecorate objects is the big drawback to this
approach.
However, Object#dup doesn’t copy singleton methods or modules included
with Object#extend, so you might be able to undecorate an object by
duping it.

Removable modules would be cool, but it would be tricky because
modules can use append_features and extend_object to perform arbitrary
transformations.

Nathan

Hi,

    def decorate(obj, name)
      orig = orig_name(name)
      (class << obj; self; end).class_eval {alias_method(orig, name)}
    end

Cool! I was wondering if there was a syntax for that, but I couldn’t
figure it out so I just gave up and used eval.

This construct sometimes appears here.

    def call_orig(obj, name, *args, &block)
      obj.__send__(orig_name(name), *args, &block)
    end

What does send do? And what’s the block for?

#send may be overridden, e.g. Socket, however, redefinition of
send is warned.

$ ruby -e ‘class Foo;def send;end;end’
-e:1: warning: redefining `send’ may cause serious problem

When the original method yields, you have to pass the given
block.

···

At Sat, 6 Dec 2003 04:12:03 +0900, Nathan Weston wrote:


Nobu Nakada

send (commonly known as send) sends a message to an object.

[1,2,3].send(:length) # => 3

send is the canonical method, in case someone overwrites send.

The block is included just so it gets passed on if provided.

Gavin

···

On Saturday, December 6, 2003, 6:12:03 AM, Nathan wrote:

  #Call the original definition of name on object
  def call_orig(obj, name, *args)
obj.send(orig_name(name), *args)
  end
    def call_orig(obj, name, *args, &block)
      obj.__send__(orig_name(name), *args, &block)
    end

What does send do? And what’s the block for?