Using Modules as Decorators

Discussion in 'Ruby' started by Nathan Weston, Dec 3, 2003.

  1. 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
    Nathan Weston, Dec 3, 2003
    #1
    1. Advertising

  2. Nathan Weston

    Guest

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

    --
    Nobu Nakada
    , Dec 4, 2003
    #2
    1. Advertising

  3. Nathan Weston

    Ara.T.Howard Guest

    On Thu, 4 Dec 2003 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'
    ===============================================================================
    Ara.T.Howard, Dec 4, 2003
    #3
  4. 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/
    Asfand Yar Qazi, Dec 4, 2003
    #4
  5. Nathan Weston

    Guest

    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
    , Dec 6, 2003
    #5
  6. 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
    Gavin Sinclair, Dec 6, 2003
    #6
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Michael Sparks

    Using metaclasses to play with decorators.

    Michael Sparks, Jun 15, 2004, in forum: Python
    Replies:
    4
    Views:
    328
    Michele Simionato
    Jun 18, 2004
  2. Michael Sparks
    Replies:
    6
    Views:
    347
    David MacQuigg
    Jun 18, 2004
  3. Robert Brewer
    Replies:
    9
    Views:
    302
    David MacQuigg
    Jun 26, 2004
  4. Arien Malec

    PEP 318 decorators are not Decorators

    Arien Malec, Aug 13, 2004, in forum: Python
    Replies:
    11
    Views:
    558
    Arien Malec
    Aug 16, 2004
  5. Ben Weintraub

    Disabling modules using Modules/Setup

    Ben Weintraub, Sep 9, 2006, in forum: Python
    Replies:
    0
    Views:
    344
    Ben Weintraub
    Sep 9, 2006
Loading...

Share This Page