鸭子字符串,符号和数组的优雅方式?

时间:2009-08-25 19:36:11

标签: ruby

这是针对我不能破解的现有公共API,但我确实希望扩展。

目前,该方法采用字符串或符号或其他任何有意义的内容作为第一个参数传递给send

我想添加发送字符串,符号等列表的功能。我可以使用is_a? Array,但还有其他方法可以发送列表,而且这不是非常重要的。

我将在列表中调用map,因此第一个倾向是使用respond_to? :map。但是字符串也会响应:map,因此无效。

8 个答案:

答案 0 :(得分:6)

如何将它们全部视为Arrays?您希望String的行为与仅包含Array的{​​{1}}相同:

String

def foo(obj, arg) [*arg].each { |method| obj.send(method) } end 技巧有效,因为splat运算符([*arg])将单个元素转换为自身,或将*转换为其元素的内联列表。

<强>后来

这基本上只是一个语法上有甜味的版本或Arnaud's answer,但如果你传递包含其他Array的{​​{1}},则会有细微差别。

稍后

Array的返回值有一个额外的区别。如果您致电Array,您可能会惊讶地发现foo。要解决此问题,您可以添加Kestrel

foo(bar, :baz)

将始终返回[baz]。或者您可以执行def foo(obj, arg) returning(arg) do |args| [*args].each { |method| obj.send(method) } end end ,以便将来电链接到arg。这取决于你想要什么样的回报价值行为。

答案 1 :(得分:2)

所有答案都忽略了一个关键细节:字符串回复:map,所以最简单的答案就在原始问题中:只需使用respond_to? :map

答案 2 :(得分:1)

由于ArrayString都是Enumerables,所以没有一种优雅的方式可以说“一个可以点击的东西,但不是一个字符串”,至少不是讨论

我要做的是对于Enumerable(responds_to? :[])的duck-type,然后使用case语句,如下所示:

def foo(obj, arg)
  if arg.respond_to?(:[])
    case arg
    when String then obj.send(arg)
    else arg.each { |method_name| obj.send(method_name) }
    end
  end
end

甚至更干净:

def foo(obj, arg)
  case arg
  when String then obj.send(arg)
  when Enumerable then arg.each { |method| obj.send(method) }
  else nil
  end
end

答案 3 :(得分:1)

假设您的函数名为func

我会使用

从参数中创建一个数组
def func(param)
  a = Array.new
  a << param
  a.flatten!
  func_array(a)
end

最终只为数组实现函数func_array

用func(“hello world”)你会得到a.flatten! =&GT; [ “你好,世界” ] 用func([“你好”,“世界”])你会得到一个! =&GT; [“你好”,“世界”]

答案 4 :(得分:0)

你能根据parameter.class.name切换行为吗?这很难看,但是如果我理解正确的话,你会有一种方法可以将多种类型传递给你 - 你必须以某种方式区分它。

或者,只需添加一个处理数组类型参数的方法。它的行为略有不同,因此额外的方法可能有意义。

答案 5 :(得分:0)

在发送对象之前,使用Marshal序列化对象。

答案 6 :(得分:0)

如果你不想monkeypatch,只需在发送之前按下列表到适当的字符串。如果你不介意monkeypatching或继承,但想保持相同的方法签名:

class ToBePatched
    alias_method :__old_takes_a_string, :takes_a_string

    #since the old method wanted only a string, check for a string and call the old method
    # otherwise do your business with the map on things that respond to a map.
    def takes_a_string( string_or_mappable )
        return __old_takes_a_string( string_or_mappable ) if String === string_or_mappable
        raise ArgumentError unless string_or_mappable.responds_to?( :map )
        # do whatever you wish to do
    end
end

答案 7 :(得分:0)

也许这个问题不够明确,但一夜的睡眠让我有两种清晰的方式来回答这个问题。

1:to_sym可在StringSymbol上使用,并且应该可用于任何像字符串一样嘎嘎作响的内容。

if arg.respond_to? :to_sym
    obj.send(arg, ...)
else
    # do array stuff
end

2:传递数组时发送抛出TypeError

begin
  obj.send(arg, ...)
rescue TypeError
  # do array stuff
end

我特别喜欢#2。我严重怀疑旧API的任何用户都期望通过此方法引发TypeError ...