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
相关产品推荐
相关产品推荐

