Overriding delegated methods

J

Jesse Merriman

I'm trying to use SimpleDelegator, but am having a problem overring a method.
Here's a boiled-down version:

$ cat delegator_test.rb
#!/usr/bin/env ruby

require 'delegate'

class A
def foo
bar()
#self.bar()
end

def bar
'original'
end
end

class B < SimpleDelegator
def initialize
super A.new
end

def bar
'overridden'
end
end

puts B.new.foo

$ ./delegator_test.rb
original


I wanted "overridden" to be output. Is there a good way to do this? Since I'm
changing the delegated object with __setobj__, subclassing won't work, and I'd
rather not have to override all methods that call overridden methods. Note that
this works:
puts B.new.bar
overridden
 
P

Pit Capitain

2007/7/21 said:
I'm trying to use SimpleDelegator, but am having a problem overring a method.
(... example: B delegates to A, A should call back to B ...)
Is there a good way to do this? Since I'm
changing the delegated object with __setobj__, subclassing won't work, and I'd
rather not have to override all methods that call overridden methods.

Jesse, can you tell us a bit more of what you are trying to achieve?
For example, in the lifetime of a B instance, how often do you change
the delegate? Do you want the set of delegated methods to change when
changing the delegate, or is there a fixed set of methods to be
delegated from B to the delegate? Can you change the source code of
the delegate? Do you need to create instances of the delegate classes
independent of B?

Regards,
Pit
 
J

Jesse Merriman

Jesse, can you tell us a bit more of what you are trying to achieve?

Sure. Basically, I have one class that wraps an external process (and provides
other goodies). It has a #puts method to send input to that process, and
receive back parsed output. #puts is called both on its own from external code,
and from within other methods of the same class. The problem then was that the
external process ate more and more memory over time (and no, this optimization
is not premature. Its a definite problem). The solution that came to my mind
was to create a delegating class that counts how many times #puts has been
called, and restarts the process by killing the old wrapper object and creating
a new one (in my app, the calls to #puts were independent of each other, so I
could get away with this). So I overrode #puts, but the calls to #puts in the
original class are not using it. I could put killing/restarting code into the
original class, but I think separating it would be a nicer solution.
For example, in the lifetime of a B instance, how often do you change
the delegate?

At regular intervals (every X calls to #puts, as above). In one specific
instance, that amounted to around every half hour over a 12 hour period.
Do you want the set of delegated methods to change when
changing the delegate, or is there a fixed set of methods to be
delegated from B to the delegate?

I want all calls to #puts, whether from the delagating or delegated class, to
go through my overridden version (but still have the original accessible via
super() or something).
Can you change the source code of the delegate?

Yeah, but I'd rather not.
 
P

Pit Capitain

2007/7/22 said:
(...) The solution that came to my mind
was to create a delegating class that counts how many times #puts has been
called, and restarts the process by killing the old wrapper object and creating
a new one (...)

Thanks for the explanation. In this case you could try something like this:

require 'delegate'

class A
def foo
bar
end

def bar
'original'
end
end

class B < SimpleDelegator
def initialize
restart_delegate
end

def restart_delegate
delegate = A.new
__setobj__ delegate
controller = self
counter = 0
class << delegate; self; end.class_eval do
define_method :bar do
counter += 1
controller.restart_delegate if counter > 2
"overridden in #{self}, original was #{super}"
end
end
end
end

b = B.new
4.times do
puts b.foo
puts b.bar
end

The output:

overridden in #<A:0x2e0b42c>, original was original
overridden in #<A:0x2e0b42c>, original was original
overridden in #<A:0x2e0b42c>, original was original
overridden in #<A:0x2e0ab94>, original was original
overridden in #<A:0x2e0ab94>, original was original
overridden in #<A:0x2e0ab94>, original was original
overridden in #<A:0x2e0a900>, original was original
overridden in #<A:0x2e0a900>, original was original

Note how the A instance changes every three calls. The counter
increases both for internal and external calls. You can call the
original method with "super".

HTH

Regards,
Pit
 
J

Jesse Merriman

Thanks for the explanation. In this case you could try something like this:

require 'delegate'
<snip>
Note how the A instance changes every three calls. The counter
increases both for internal and external calls. You can call the
original method with "super".

Yep, that works just how I want it. I don't particularly like having to
define #bar everytime it restarts, but it'll do. Thanks, Pit.
 
P

Pit Capitain

2007/7/23 said:
Yep, that works just how I want it. I don't particularly like having to
define #bar everytime it restarts, but it'll do.

Yes, it isn't nice, but given your constraints there's not much you
can do. Maybe instead of defining #bar everytime you'd prefer using a
module:

class B < SimpleDelegator
def initialize
restart_delegate
end

def restart_delegate
delegate = A.new
delegate.extend WrapperModule
delegate.init_wrapper self
__setobj__ delegate
end

module WrapperModule
def init_wrapper wrapper
@wrapper = wrapper
@counter = 0
end

def bar
@counter += 1
@wrapper.restart_delegate if @counter > 2
"overridden in #{self}, original was #{super}"
end
end
end

Note that there could be a name conflict between the instance
variables of the module and those of the delegate class.
Thanks, Pit.

You're welcome. Thanks for the puzzle :)

Regards,
Pit
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top