0

我是一位经验丰富的 Obj-C/Java 程序员,并且正在研究 Ruby。显然,它是如此动态的事实很棒(重新开放课程很棒!)但是当我开始编写 Ruby 代码时,有一件事让我感到烦恼/担心。

我很想知道你们 Ruby-ers 做了什么(如果有的话)来明确地在自己的类中设置 iVar 的类型。据我所知,您可以为任何对象设置 iVar,而 ruby​​ 不会抱怨。但是,如果您希望特定 iVar 属于某种类型,那么它可能会导致问题。例如:

class MyString
  def initialize(myString)
    @myString = myString
  end

  def uppercase_my_string
    @myString.upcase
  end
end

st1 = MyString.new("a string!")
st1.uppercase_my_string

st2 = MyString.new(["a string"])
st2.uppercase_my_string

这段代码会抛出一个NoMethodError,因为数组当然没有方法upcase。不幸的是,它并没有告诉我们真正出错的地方(上面的行,创建时str2)所以我们在调试时没有多大帮助(如果str2碰巧在一些不显眼的地方创建了几个模块)一个自然的步骤可能是添加一些检查initialize如下:

class MyString
  def initialize(myString)
    raise TypeError, "myString iVar is not a string!" unless myString.class == String
    @myString = myString
  end
end
...same code as before

太好了,现在如果我们不小心创建了一个新的 MyString,我们会被告知我们是多么愚蠢(更重要的是,当我们这样做而不是失败时,我们会被告知。打字有点痛苦,但没关系。我的下一个问题是我们决定attr_accessors在 iVar 上使用的时候。

class MyString
  attr_accessor :my_string
  def initialize(my_string)
    raise TypeError, "myString iVar is not a string!" unless my_string.class == String
    @my_string = my_string
  end

  def uppercase_my_string
    @my_string.upcase
  end
end

st1 = MyString.new("a string!")
st1.uppercase_my_string

st2 = MyString.new("good, it's a string")
st2.my_string = ["an array!"]
st2.uppercase_my_string

使用定义的 setter,我们可以偷偷摸摸地绕过错误检查initialize。这再次存在抛出异常的问题,uppercase_my_string而不是当我们意外设置@my_string为数组时。

最后,我们可以手动创建访问器并添加错误检查,但这是一个巨大的痛苦......有没有更快更简单的方法来做到这一点。还是我只是思想封闭,不够有活力?

谢谢!


旁白:我知道在 Obj-C 中你在运行时仍然有同样的问题,但通常你会发现编译器错误,说你正在将一个类型的对象分配给一个类型array的变量string(或类似的东西),所以至少我们被警告发生在哪里

4

2 回答 2

4

在实践中,这类类型问题实际上非常罕见。如果你想变得偏执(因为他们真的是你得到你),你可以发送你的输入to_s以确保你总是有一个字符串:

def initialize(my_string)
  @my_string = my_string.to_s
end

然后你可以说MyString.new(6),一切都会按预期工作。当然,你可以MyString.new([6, 11])胡说八道。

如果你真的想my_string成为一个字符串,你不会明确地检查它的class. 如果有人将 String 子类化,那将导致问题,因此您至少要使用is_a?

def initialize(myString)
  raise TypeError, ... unless myString.is_a? String
  @myString = myString
end

to_str您还可以检查一种方法:

def initialize(myString)
  raise TypeError, ... unless myString.respond_to? :to_str
  @myString = myString.to_str
end

实现该方法将(在某些圈子中)表明您的事物与字符串类似,足以成为字符串。不过,我认为调用to_s会是一个更好的主意,这将使事情的行为更像人们在 Ruby 中所期望的那样。

就您的变异器问题而言:

st2.my_string = ["an array!"]

您不必让任何人将他们想要的任何内容写入您的属性:类不是结构。您只能自动定义访问器并编写自己的 mutator 以自动滑入您的to_s调用:

class MyString
  attr_reader :my_string
  def initialize(my_string)
    self.my_string = my_string
  end

  def my_string=(s)
    @my_string = s.to_s
  end

  def uppercase_my_string
    @my_string.upcase
  end
end

基本上,您不必担心 Ruby 中的类型,您会担心某些响应的方法。而且,如果您希望某个特定的东西成为一个字符串,您可以通过调用通用to_s方法将它变成一个字符串(这就是字符串插值,"#{x}", 将做的)。

于 2013-05-19T07:15:34.023 回答
2

我认为如果您的程序将错误类型的对象传递给一个类,那么这并不是一个应该通过实例化或变异的对象来解决的问题。即使有额外的类型检查和错误抛出,您的程序仍然将错误的对象传递给您的类,并且不会真正使您的程序不那么不正确。

在使用MyString该类的程序中,应该有一个约定,例如提供给构造函数的对象实现该upcase方法的协议。

还采用测试和文档来禁止程序中的此类缺陷。但我认为这可能是一个单独的讨论。

于 2013-05-19T22:28:35.643 回答