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

Ruby中让方法适配Grouper鸭式类型/函数式接口的简便方案

如何让现有方法适配单方法鸭式类型(函数式接口)

假设库定义了如下协议:

class Grouper
  # 将一组元素分组为数组的数组(即分组后的数组)
  def group_together(things); end
end

group_together的契约很简单,我已经有大量相关逻辑以方法和代码块的形式存在:

module CommonGroupers
  def one_per_group(things)
    things.map { |x| x }
  end

  def one_group(things)
    [things.dup]
  end

  def geometric(things)
    result = []
    things.each_with_index do |thing, i|
      result << [] if i & (i + 1) == 0
      result[-1] << thing
    end
    result
  end

  def sorted_by(delegate, &block)
    lambda do |things|
      delegate.group_together(things.sort_by(&block))
    end
  end

  def partition_by(delegate, &block)
    lambda do |things|
      # 省略实现
    end
  end

  # 更多方法...
end

我想找简便的方式让这些方法遵循Grouper协议,目前已经有两种实现方案,想知道有没有Ruby内置的、更符合惯用风格的实现方式?


现有方案

方案一:包装Proc为Grouper实例

class ProcGrouper
  def initialize(&block)
    @proc = block
  end

  def group_together(things)
    @proc.call(things)
  end
end

module CommonGroupers
  def geometric
    ProcGrouper.new do |things|
      result = []
      things.each_with_index do |thing, i|
        result << [] if i & (i + 1) == 0
        result[-1] << thing
      end
      result
    end
  end
end

# 使用方式
CommonGroupers.geometric.group_together 1..10

方案二:动态给Proc添加别名方法

def grouper(&block)
  class << block
    alias_method :group_together, :call
  end
  block
end

module CommonGroupers
  def geometric
    grouper do |things|
      result = []
      things.each_with_index do |thing, i|
        result << [] if i & (i + 1) == 0
        result[-1] << thing
      end
      result
    end
  end
end

# 使用方式
CommonGroupers.geometric.group_together 1..10

更符合Ruby惯用风格的实现方式

方式1:匿名类+模块混入

直接利用Ruby的匿名类和模块混入,快速将现有方法包装为Grouper实例,无需额外定义包装类:

module CommonGroupers
  def self.grouper_for(method_name)
    Class.new do
      include CommonGroupers

      def initialize(method_name)
        @method_name = method_name
      end

      def group_together(things)
        send(@method_name, things)
      end
    end.new(method_name)
  end

  # 原有方法保持不变...
end

# 使用方式
CommonGroupers.grouper_for(:geometric).group_together(1..10)

如果是处理代码块(比如sorted_by返回的lambda),可以用Struct简化包装逻辑:

GrouperProc = Struct.new(:block) do
  def group_together(things)
    block.call(things)
  end
end

# 使用sorted_by时
sorted_grouper = GrouperProc.new(CommonGroupers.sorted_by(some_delegate) { |x| x[:key] })
sorted_grouper.group_together(items)

方式2:利用标准库SimpleDelegator

Ruby标准库的SimpleDelegator可以快速将Proc包装为符合Grouper协议的对象,避免手动定义包装类:

require 'delegate'

class GrouperDelegator < SimpleDelegator
  def group_together(things)
    __getobj__.call(things)
  end
end

# 使用方式
geometric_grouper = GrouperDelegator.new(CommonGroupers.method(:geometric).to_proc)
geometric_grouper.group_together(1..10)

方式3:改进版方案二(避免全局污染)

方案二直接修改了Proc的 eigenclass,可能影响全局所有Proc实例,改成仅给当前block添加单例方法更安全:

def grouper(&block)
  block.define_singleton_method(:group_together, &block.method(:call))
  block
end

总结

  • 转换模块方法时,匿名类+混入的方式最直观,贴合Ruby面向对象风格;
  • 处理代码块/Proc时,Struct或SimpleDelegator是标准库提供的简洁工具;
  • 改进版方案二通过单例方法定义,避免了全局Proc类的污染,更安全可控。

内容的提问来源于stack exchange,提问作者Michael Deardeuff

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 13:53:15