在Ruby中明确设置iVar类(ala Obj-C)

时间:2013-05-19 06:46:49

标签: ruby class ivar

我是一位经验丰富的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,我们就会被告知我们有多傻(更重要的是,当我们这样做时我们会被告知,而不是当我们失败时。有点痛苦,但是没关系。 我的下一个问题是当我们决定在iVar上使用attr_accessors时。

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类型的对象分配给类型为{的变量{1}}(或类似的东西),所以至少我们会被警告它发生的地方

2 个答案:

答案 0 :(得分:4)

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

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

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

如果您真的希望my_string成为字符串,则不会明确检查其class。如果某人有子类字符串,那么这将导致问题,因此您至少要使用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

实现该方法会(在某些圈子中)表明你的东西是String类似于String。我认为调用to_s会是一个更好的主意,这会使事情表现得像人们在Ruby中所期望的那样。

就你的mutator问题而言:

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}")来使它成为一个字符串。

答案 1 :(得分:2)

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

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

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