一、Method对象是什么?

在Ruby中,Method对象是一个很有趣的东西。你可以把它理解为一个"方法的快照",它把方法本身变成了一个可以传递的对象。想象一下,你有一个工具箱,里面的工具(方法)本来是固定在工具箱里的,但Method对象允许你把工具拆下来单独使用。

举个例子(技术栈:Ruby 3.2):

class Calculator
  def add(a, b)
    a + b
  end
end

calc = Calculator.new
method_add = calc.method(:add)  # 把add方法变成Method对象
puts method_add.call(3, 5)      # 输出:8

这里的method_add就是一个Method对象,它保存了calc实例的add方法,可以像普通对象一样被调用。

二、动态委托的魔法

方法委托的核心思想是"让别人帮你干活"。Ruby的Method对象让这种委托变得极其灵活。比如我们有个Logger类需要把某些方法委托给FileWriter

class FileWriter
  def write_log(content)
    "写入文件: #{content}"
  end
end

class Logger
  attr_accessor :writer
  
  def initialize
    @writer = FileWriter.new
  end
  
  # 动态委托示例
  def method_missing(method_name, *args)
    if writer.respond_to?(method_name)
      writer.method(method_name).call(*args)  # 关键委托逻辑
    else
      super
    end
  end
end

logger = Logger.new
puts logger.write_log("用户登录")  # 实际调用的是FileWriter的方法

通过method_missing捕获未知方法,再用Method对象转发调用,这就是动态委托的经典实现。

三、高级技巧:批量委托

实际开发中我们可能需要批量委托多个方法。Ruby的Module#delegate方法可以帮我们,但用Method对象也能实现类似功能:

class UserService
  def find_by_id(id) = "用户#{id}"
  def find_by_name(name) = "姓名#{name}"
end

class ApiController
  def initialize
    @service = UserService.new
    delegate_methods :find_by_id, :find_by_name
  end
  
  private
  
  # 动态创建委托方法
  def delegate_methods(*methods)
    methods.each do |method_name|
      define_method(method_name) do |*args|
        @service.method(method_name).call(*args)
      end
    end
  end
end

controller = ApiController.new
puts controller.find_by_id(100)  # 输出:"用户100"

这里通过define_method动态创建方法,每个方法内部都用Method对象转发调用。

四、实战:带条件的委托

有时候我们需要根据条件决定是否委托。比如权限检查:

class AdminOperation
  def delete_user(id) = "删除用户#{id}"
end

class Proxy
  def initialize(admin)
    @admin = admin
    @access = false
  end
  
  def grant_access! = @access = true
  
  # 条件委托示例
  def delete_user(id)
    if @access
      @admin.method(:delete_user).call(id)
    else
      "无权限操作"
    end
  end
end

proxy = Proxy.new(AdminOperation.new)
puts proxy.delete_user(99)  # 输出:"无权限操作"
proxy.grant_access!
puts proxy.delete_user(99)  # 输出:"删除用户99"

这种模式在实现权限控制、缓存等场景非常有用。

五、技术深潜:Method对象的本质

每个Method对象都绑定了接收者(receiver)和方法定义。你可以通过以下方式查看它的内部信息:

m = "hello".method(:upcase)
puts m.receiver  # 输出:"hello"
puts m.name      # 输出::upcase
puts m.owner     # 输出:String

有趣的是,Method对象还支持转换为Proc,这让它可以用于迭代器等场景:

numbers = [1, 2, 3]
double = 2.method(:*)
puts numbers.map(&double)  # 输出:[2, 4, 6]

六、应用场景与注意事项

典型应用场景

  1. 实现装饰器模式
  2. 构建中间件管道
  3. 动态代理远程调用
  4. AOP(面向切面编程)实现

技术优缺点
✅ 优点:

  • 运行时灵活性极高
  • 代码可读性好
  • 避免重复定义方法

❌ 缺点:

  • 过度使用会导致调试困难
  • 性能略低于直接方法调用

重要注意事项

  1. 注意方法可见性问题(private方法需要特殊处理)
  2. 委托链不宜过长
  3. 在Rails等框架中注意与delegate方法的配合使用

七、完整示例:智能代理

最后来看一个综合示例,实现一个可以记录执行时间的代理:

class RealWorker
  def heavy_task
    sleep(1)
    "任务完成"
  end
end

class SmartProxy
  def initialize(worker)
    @worker = worker
  end
  
  # 动态代理+耗时统计
  def method_missing(method, *args)
    if @worker.respond_to?(method)
      start_time = Time.now
      result = @worker.method(method).call(*args)
      elapsed = (Time.now - start_time).round(2)
      puts "方法 #{method} 执行耗时:#{elapsed}秒"
      result
    else
      super
    end
  end
end

proxy = SmartProxy.new(RealWorker.new)
puts proxy.heavy_task  # 输出执行时间后返回"任务完成"

通过这个模式,我们可以轻松给现有类添加新功能而不修改原始类。

Ruby的Method对象就像一把瑞士军刀,虽然小巧但功能强大。掌握它可以让你的代码更加灵活优雅,但也要注意避免过度设计。在实际项目中,建议先考虑简单方案,只有在真正需要动态行为时才使用这种高级特性。