Created
March 4, 2011 22:30
-
-
Save forforf/855828 to your computer and use it in GitHub Desktop.
A class that can delegate class and instance methods to multiple delegates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#classes to delegate to | |
############# | |
class A | |
class << self; attr_accessor :_me; end | |
attr_accessor :me | |
def initialize | |
@me = :aa | |
end | |
def self._i_am | |
:A | |
end | |
def i_am | |
#sleep 2 | |
:a | |
end | |
end | |
class B | |
class << self; attr_accessor :_me; end | |
attr_accessor :me | |
def initialize | |
@me = :bb | |
end | |
def self._i_am | |
:B | |
end | |
def self._i_am_not | |
[:A, :C] | |
end | |
def i_am | |
#sleep 1 | |
:b | |
end | |
def i_am_not | |
[:a, :c] | |
end | |
end | |
class C | |
class << self; attr_accessor :_me; end | |
attr_accessor :me | |
def initialize | |
@me = :cc | |
end | |
def self._i_am | |
:C | |
end | |
def self._i_am_not | |
[:A, :B] | |
end | |
def self._next | |
:D | |
end | |
def i_am | |
:c | |
end | |
def i_am_not | |
[:a, :b] | |
end | |
def next | |
:d | |
end | |
end | |
################ | |
class Forwarder | |
attr_accessor :proxies, :valid_response_trace, :invalid_response_list | |
def initialize(proxies) | |
@proxies = proxies | |
end | |
#execute forwarding in an iterative fashion | |
IterativeBlock = lambda{ |proxies, m, *args, &block| | |
all_resps = {} | |
proxies.each do |proxy| | |
this_resp = {} | |
begin | |
#collect valid responses | |
all_resps[:valid] ||= {} | |
all_resps[:valid][proxy] = proxy.__send__(m, *args, &block) | |
rescue NoMethodError | |
#oops we have some invalid responses, collect those too | |
all_resps[:invalid] ||= [] | |
all_resps[:invalid] << proxy | |
end | |
end | |
all_resps | |
} | |
#If we like speed (with a dash of danger) we can thread the requests rather than iterate | |
ThreadedBlock= lambda{ |proxies, m, *args, &block| | |
all_resps = {} | |
threads = [] | |
proxies.each do |proxy| | |
threads << Thread.new do | |
begin | |
Thread.current[:resp] = proxy.__send__(m, *args, &block) | |
rescue | |
Thread.current[:err] = proxy | |
end | |
end | |
threads.each do |t| | |
t.join | |
if t[:resp] | |
#valid response | |
all_resps[:valid] ||= {} | |
all_resps[:valid][proxy] = t[:resp] | |
elsif t[:err] | |
#oops error | |
all_resps[:invalid] ||= [] | |
all_resps[:invalid] << t[:err] | |
else | |
raise "Proxy returned an invalid responseto its thread."\ | |
"Response thread: #{t} Proxy: #{proxy.inspect}" | |
end | |
end | |
end | |
all_resps | |
} | |
#More speed (with more danger) we can use the first valid response (note the change in t.join) | |
#How to react to the first response, maybe fibers? | |
ThreadedBlock= lambda{ |proxies, m, *args, &block| | |
#send method to all | |
#respond on first response | |
#let other methods respond as well (but main program has moved on | |
#I don't want to kill threads because that will create indertiminate state | |
} | |
def forward_to_proxy(call_block, m, *args, &block) | |
@valid_response_trace = {} | |
@invalid_response_list = [] | |
proxies = @proxies | |
if proxies.size > 0 | |
resp = call_block.call(proxies, m, *args, &block) | |
@valid_response_trace = resp[:valid] | |
else | |
raise "No Proxies assigned" | |
end | |
@valid_response_trace | |
end | |
def forward_iterative(m, *args, &block) | |
forward_to_proxy(IterativeBlock, m, *args, & block) | |
end | |
def forward_threaded(m, *args, &block) | |
forward_to_proxy(ThreadedBlock, m, *args, & block) | |
end | |
end | |
class MultiProxy | |
#unload these methods so the proxy object will handle them | |
[:to_s,:inspect,:=~,:!~,:===].each do |m| | |
undef_method m | |
end | |
#proxied_classes is the container the classes that will be proxied | |
class << self | |
#unload these methods so the proxy class will handle them | |
[:to_s,:inspect,:=~,:!~,:===].each do |m| | |
undef_method m | |
end | |
attr_accessor :class_forwarder, :class_proxy_methods, :all_proxied_class_methods | |
def proxied_classes=(classes_to_proxy) | |
@proxy_classes = classes_to_proxy || [] | |
@class_proxy_methods = {} | |
@proxies = classes_to_proxy | |
@proxies.each do |proxy| | |
proxy.methods.each do |proxy_method| | |
if @class_proxy_methods[proxy_method] | |
@class_proxy_methods[proxy_method] << proxy | |
else | |
@class_proxy_methods[proxy_method] = [proxy] | |
end | |
end | |
end | |
@all_proxied_class_methods = @class_proxy_methods.keys | |
@class_forwarder = Forwarder.new(@proxy_classes) if @proxy_classes.size > 0 | |
end | |
def proxied_classes | |
@proxy_classes | |
end | |
end | |
MultiProxy.proxied_classes = [] | |
def self.method_missing(m, *args, &block) | |
if MultiProxy.all_proxied_class_methods.include? m | |
resp = MultiProxy.class_forwarder.forward_iterative(m, *args, &block) | |
#resp.values | |
else | |
raise NoMethodError, "MultiProxy can't find the class method #{m} in any of its proxies" | |
end | |
end | |
attr_accessor :proxied_objects, :proxied_methods | |
def initialize(init_params = {}) | |
@proxied_objects = [] | |
@proxied_methods = {} | |
raise "No Proxied Classes set" unless MultiProxy.proxied_classes.size > 0 | |
MultiProxy.proxied_classes.each do |proxy_class| | |
proxied_obj = proxy_class.new #(init_params) | |
proxied_obj.methods.each do |proxy_method| | |
if @proxied_methods[proxy_method] | |
@proxied_methods[proxy_method] << proxied_obj | |
else | |
@proxied_methods[proxy_method] = [proxied_obj] | |
end | |
end | |
@proxied_objects << proxied_obj | |
end | |
@obj_forwarder = Forwarder.new(@proxied_objects) | |
end | |
def method_missing(m, *args, &block) | |
if @proxied_methods.include? m | |
resp = @obj_forwarder.forward_iterative(m, *args, &block) | |
#resp.values | |
else | |
raise NoMethodError, "MultiProxy object can't find the method #{m} in any of its proxies" | |
end | |
end | |
end | |
#TESTING | |
MultiProxy.proxied_classes = [A, B, C] | |
p MultiProxy.class_proxy_methods | |
puts "--" | |
p MultiProxy.to_s | |
p MultiProxy._i_am_not.values | |
#p MultiProxy.foo | |
puts "--" | |
x = MultiProxy.new | |
#p x.to_s | |
p x.i_am_not.values | |
p x.foo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment