Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > Ruby > Using Modules as Decorators

Reply
Thread Tools

Using Modules as Decorators

 
 
Nathan Weston
Guest
Posts: n/a
 
      12-03-2003
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(rig_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
 
Reply With Quote
 
 
 
 
nobu.nokada@softhome.net
Guest
Posts: n/a
 
      12-04-2003
Hi,

At Thu, 4 Dec 2003 07:17:07 +0900,
Nathan Weston wrote:
> def decorate(obj, name)
> #We have to eval string to avoid scope issues:
> #class << obj
> # alias_method(rig_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

--
Nobu Nakada


 
Reply With Quote
 
 
 
 
Ara.T.Howard
Guest
Posts: n/a
 
      12-04-2003
On Thu, 4 Dec 2003 http://www.velocityreviews.com/forums/(E-Mail Removed) wrote:

<snip>
> (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'
================================================== =============================

 
Reply With Quote
 
Asfand Yar Qazi
Guest
Posts: n/a
 
      12-04-2003
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?



--
http://www.it-is-truth.org/

 
Reply With Quote
 
nobu.nokada@softhome.net
Guest
Posts: n/a
 
      12-06-2003
Hi,

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

--
Nobu Nakada


 
Reply With Quote
 
Gavin Sinclair
Guest
Posts: n/a
 
      12-06-2003
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?


__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



 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Disabling modules using Modules/Setup Ben Weintraub Python 0 09-09-2006 12:04 AM
PEP 318 decorators are not Decorators Arien Malec Python 11 08-16-2004 06:38 PM
Using metaclasses to play with decorators. Michael Sparks Python 4 06-18-2004 06:25 AM
Re: Using metaclasses to play with decorators. Michael Sparks Python 6 06-18-2004 05:04 AM



Advertisments