Using Modules as Decorators

N

Nathan Weston

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
 
N

nobu.nokada

Hi,

At Thu, 4 Dec 2003 07:17:07 +0900,
Nathan said:
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
 
A

Ara.T.Howard

On Thu, 4 Dec 2003 (e-mail address removed) wrote:

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

_that_ is too cool

-a
--

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 :: http://www.ngdc.noaa.gov/stp/
| 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'
===============================================================================
 
A

Asfand Yar Qazi

Nathan said:
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?
 
N

nobu.nokada

Hi,

At Sat, 6 Dec 2003 04:12:03 +0900,
Nathan said:
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.
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.
 
G

Gavin Sinclair

What does __send__ do? And what's the block for?

__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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top