You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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个参数

我的疑问

  1. 我当前的实现思路是否正确?
  2. 使用SimpleDelegator是不是实现这个需求最简单的方案?
  3. 我现在需要给现有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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:15:03