Ruby可扩展动态代理类实现咨询:SimpleDelegator是否适用?
给定初始类定义
class Application def self.open_current return Current.new() end end class Current def get_row(row) Row.new(row) end end class Row def get_col(row) #... end end
需求目标
需要设计一个动态Proxy类,满足以下要求:
- 为每个类自动创建
Proxy<<Class>>版本,并且支持手动扩展这个代理类,比如:class ProxyApplication def myMethod() #... end end #... - 自动包装所有类中所有方法的返回值,确保始终返回代理类而非原类实例,示例效果:
app = Proxy.new(Application) # app 是 ProxyApplication 实例 current = app.open_current # current 是 ProxyCurrent 实例 - Proxy的定义必须是动态的,不能静态硬编码每个类的代理。
目前问题
我已经花了约6小时写了相关代码,包含初始类设置、Proxy类定义和测试部分。现在的情况是:
pApplication = Proxy.new(Application)返回的是#<Proxy>(预期应该是ProxyApplication)pApplication.open_current能返回#<ProxyCurrent>,这个方向是对的- 但调用测试方法
test()时出现参数错误:delegate.rb尝试传入2、3个参数,而方法实际需要0个参数
我的疑问
- 我当前的实现思路是否正确?
- 使用
SimpleDelegator是不是实现这个需求最简单的方案? - 我现在需要给现有
SimpleDelegator添加新功能,也考虑过Forwardable,但不想手动委托方法,有没有可行的实现思路?
关于你的实现思路与方案选择
首先,你的整体方向是完全正确的——动态生成代理类并自动包装返回值,这确实是解决这类问题的核心思路。不过从Proxy.new(Application)返回#<Proxy>而非预期的ProxyApplication来看,你的动态类生成逻辑存在小瑕疵,需要调整。
1. SimpleDelegator是不是最优选择?
SimpleDelegator确实是Ruby里实现委托的便捷工具,但它本质是实例级的委托方案,更适合给单个实例做包装,而你的需求是要生成可扩展的类级代理(比如ProxyApplication是一个独立类,还能手动加方法)。直接用SimpleDelegator的话,在动态类生成和方法扩展上会比较受限,容易出现你遇到的参数传递这类兼容性问题。
相比之下,基于Class.new动态生成代理类,结合method_missing自动处理方法委托和返回值包装,会更灵活,也更贴合你“动态+可扩展”的需求。
2. 可行的实现思路
这里给你一个具体的落地方向,分四步走:
第一步:动态生成代理类
我们先做一个Proxy模块,当调用Proxy.wrap(klass)时,自动生成对应的Proxy<<Klass>>类(比如ProxyApplication对应Application)。如果这个代理类已经存在(比如你手动扩展过),就直接复用它。
module Proxy def self.wrap(klass) proxy_class_name = "Proxy#{klass.name}" # 检查代理类是否已存在,不存在则动态生成 unless Object.const_defined?(proxy_class_name) proxy_class = Class.new do attr_reader :target def initialize(target) @target = target end end Object.const_set(proxy_class_name, proxy_class) end Object.const_get(proxy_class_name) end end
第二步:自动委托方法并包装返回值
在动态生成的代理类中,通过method_missing捕获所有方法调用,转发给目标实例/类,然后把返回值自动包装成对应代理类:
module Proxy def self.wrap(klass) proxy_class_name = "Proxy#{klass.name}" unless Object.const_defined?(proxy_class_name) proxy_class = Class.new do attr_reader :target def initialize(target) @target = target end def method_missing(name, *args, &block) if @target.respond_to?(name, true) result = @target.send(name, *args, &block) # 仅对我们需要代理的类实例进行包装 if result.is_a?(Object) && !result.is_a?(Numeric) && !result.is_a?(String) && !result.is_a?(Array) && !result.is_a?(Hash) proxy_result_class = Proxy.wrap(result.class) proxy_result_class.new(result) else result end else super end end def respond_to_missing?(name, include_private = false) @target.respond_to?(name, include_private) || super end end Object.const_set(proxy_class_name, proxy_class) end Object.const_get(proxy_class_name) end end
这里要注意两个关键点:
- 区分类方法和实例方法:因为
Application.open_current是类方法,所以当我们用Proxy.wrap(Application).new(Application)时,@target是Application类,调用open_current会直接转发给类方法。 - 必须实现
respond_to_missing?:否则respond_to?会返回错误结果,导致后续方法调用出错。
第三步:支持手动扩展代理类
因为我们动态生成的代理类是挂在Object下的公开常量(比如ProxyApplication),所以你完全可以像需求里那样手动扩展它:
class ProxyApplication def my_custom_method puts "This is a custom method only for ProxyApplication!" end end
第四步:测试验证
现在来验证效果:
# 包装Application类 proxy_app = Proxy.wrap(Application).new(Application) puts proxy_app.class # => ProxyApplication proxy_app.my_custom_method # 输出自定义方法内容 # 调用类方法,返回包装后的Current current = proxy_app.open_current puts current.class # => ProxyCurrent # 调用Current的方法,返回包装后的Row row = current.get_row(1) puts row.class # => ProxyRow
3. 解决你遇到的参数错误问题
你提到的delegate.rb参数错误,大概率是SimpleDelegator的内置转发逻辑和你的自定义逻辑冲突导致的——比如它在处理类方法和实例方法时没有区分,导致参数传递异常。
用上面的method_missing方案,我们明确控制了参数的传递(*args, &block直接转发),就能避免这类问题。
内容的提问来源于stack exchange,提问作者Sancarn

