在Ruby中重载

Overloading in Ruby

我想在Ruby中使用重载特性,就像许多其他语言一样,但Ruby本身不支持这个特性。

我是否必须使用使用*args参数定义方法并确定方法中参数的数量和类型来实现它?有些人喜欢:

1
2
3
4
5
6
7
8
9
10
11
class A
    def foo(*args)
        case (args.length)
        when 1
            do something
        when 2
            do something-else
        ....
        end
    end
end

你可以看到,它确实比直接超载更丑。

我想知道是否有任何关键字或其他方式(如元编程模块)可以让我以更优雅的方式定义重载方法。


你可以尝试一些元编程来达到你的目标。

请参见以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class OverloadError < ArgumentError; end
class Class
=begin rdoc

=end

  def define_overload_method( methodname, *methods )    
    methods.each{ | proc |
      define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
    }
    define_method(methodname){|*x|
      if respond_to?("#{methodname}_#{x.size}")
      send"#{methodname}_#{x.size}", *x
      else
        raise OverloadError,"#{methodname} not defined for #{x.size} parameters"
      end
    }

  end
end

class X
  define_overload_method :ometh,
        Proc.new{"Called me with no parameter" },
        Proc.new{ |p1|"Called me with one parameter (#{p1.inspect})" },
        Proc.new{ |p1,p2|"Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

x = X.new

p '----------'
p x.ometh()
p x.ometh(1)
p x.ometh(1,2)
p x.ometh(1,2,3)  #OverloadError

可以使用define_overload_method定义重载方法。参数是方法名和过程列表。创建方法methodname并调用相应的方法。哪个方法由参数的数量决定(不是类型!).

另一种语法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class OverloadError < ArgumentError; end
class Class
  def def_overload( methodname)    
    define_method(methodname){|*x|
      if respond_to?("#{methodname}_#{x.size}")
      send"#{methodname}_#{x.size}", *x
      else
        raise OverloadError,"#{methodname} not defined for #{x.size} parameters"
      end
    }
  end
  def overload_method( methodname, proc )    
    define_method("#{methodname}_#{proc.arity}".to_sym, &proc )
  end
end
class X
  def_overload :ometh
  overload_method :ometh, Proc.new{"Called me with no parameter" }
  overload_method :ometh, Proc.new{ |p1|"Called me with one parameter (#{p1.inspect})" }
  overload_method :ometh, Proc.new{ |p1,p2|"Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

def_overload定义重载方法的框架,overload_method定义一个"重载方法"。

但正如霍尔格所说:

You should try to adapt to the Ruby way. There is a reason why there is no overloading in Ruby. Methods should only do one thing, not magically decide to do vastly different things just because of different arguments. Instead try to take advantage of Duck Typing and if in doubt, use different methods with meaningful names.

我很好奇如何实现具有类型敏感重载的版本。这里是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class OverloadError < ArgumentError; end
class Class
  def def_overload( methodname)    
    define_method(methodname){|*x|
      methname ="xxx"
      methname ="#{methodname}_#{x.size}#{x.map{|p| p.class.to_s}.join('_')}"
      if respond_to?(methname)
        send methname, *x
      elsif respond_to?("#{methodname}_#{x.size}")
        send"#{methodname}_#{x.size}", *x
      else
        raise OverloadError,"#{methodname} not defined for #{x.size} parameters"
      end
    }
  end
  def overload_method( methodname, *args, &proc )    
    types = []
    args.each{|arg| types << arg.to_s}
    define_method("#{methodname}_#{proc.arity}#{types.join('_')}".to_sym, &proc )
  end
end
class X
  def_overload :ometh
  overload_method(:ometh){"Called me with no parameter" }
  overload_method(:ometh, String ){ |p1|"Called me with one string parameter (#{p1.inspect})" }
  overload_method(:ometh ){ |p1|"Called me with one parameter (#{p1.inspect})" }
  overload_method(:ometh){ |p1,p2|"Called me with two parameter (#{p1.inspect}, #{p2.inspect})" }
end

当你打电话给

1
2
p x.ometh(1)
p x.ometh('a')

你得到

1
2
   "Called me with one parameter (1)"
   "Called me with one string parameter ("a")"


您可以单独测试每个参数的存在性,因为如果不通过,它们将被设置为零(假设它们是按顺序传递的!).

如果您坚持使用非常不同的参数,我建议您对每个参数使用带有符号的哈希参数。以及适当的测试。

**更新**

此外,还可以重命名使用更具体名称重载的方法,例如

1
def perform_task_with_qualifier_1


很少有gems能为您的ruby代码提供这个特性。

  • 功能红宝石
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
           defn(:greet, :male) {
             puts"Hello, sir!"
           }

           defn(:greet, :female) {
             puts"Hello, ma'am!"
           }

           foo.greet(:male)   =>"Hello, sir!"
           foo.greet(:female) =>"Hello, ma'am!"

    您可以从这里找到更多类似于长生不老药的模式匹配功能

  • 合同.ruby
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
           Contract 1 => 1
           def fact x
             x
           end

           Contract C::Num => C::Num
           def fact x
            x * fact(x - 1)
           end

    这个宝石有助于纠正漂亮的防御代码。对表现有一些批评。所以基准和决定。更多示例


    重载的定义特征是调度是静态发生的。在Ruby中,分派总是动态发生的,没有其他方法。因此,在Ruby中不可能重载。