如何在 Ruby 中将实例变量设为私有?

Posted

技术标签:

【中文标题】如何在 Ruby 中将实例变量设为私有?【英文标题】:How to make instance variables private in Ruby? 【发布时间】:2011-01-09 01:40:05 【问题描述】:

有没有办法在 ruby​​ 中使实例变量“私有”(C++ 或 Java 定义)?换句话说,我希望以下代码导致错误。

class Base
  def initialize()
    @x = 10
  end
end

class Derived < Base
  def x
    @x = 20
  end
end

d = Derived.new

【问题讨论】:

这似乎是一个有点不寻常的请求,这种模式的用例是什么?也许你知道一些我不知道的东西,这在未来会有用。 来自 C++ 世界,对我来说,在基类中拥有无法在派生类中访问的私有变量看起来很自然,这让我很有信心在派生类中不会对其进行修改班级。在上面的例子中,如果可以将其设为私有实例变量,我可以确定 @x 将被修改的唯一位置是在类“Base”中。 我认为您不应该尝试在 Ruby 中编写 C++ 代码。由于 Ruby 是一种非常动态且功能强大的语言,因此总会有一种方法可以获取私有数据。 你能给我一个更具体的用例吗?它不必是一个复杂的。我觉得如果我理解了一个你不希望对象能够访问它自己的槽的问题,它可能会有助于讨论。 【参考方案1】:

与 Ruby 中的大多数东西一样,实例变量并不是真正的“私有”,任何使用d.instance_variable_get :@x 的人都可以访问。

与 Java/C++ 不同,Ruby 中的实例变量始终是私有的。它们永远不会像方法那样成为公共 API 的一部分,因为它们只能用那个冗长的 getter 来访问。因此,如果您的 API 有任何合理性,您不必担心有人滥用您的实例变量,因为他们将使用这些方法。 (当然,如果有人想疯狂访问私有方法或实例变量,没有办法阻止他们。)

唯一担心的是,如果有人在扩展您的类时不小心覆盖了实例变量。这可以通过使用不太可能的名称来避免,在您的示例中可能称之为@base_x

【讨论】:

问题不在于他可以在他的代码中修改派生类中的@x 变量吗?这与 C++ 中派生类不能 访问私有数据成员的情况相反。因此,虽然“ruby 中的实例变量是私有的”确实如此——但重要的是,它与 C++ 中的私有含义不同 我认为用 C++ 的说法,有人会说“ruby 中的实例变量总是受到保护”。虽然不是一个完美的类比,但它比私有的 C++ 含义更准确。 叹息,是的...加入无法正确实现 OOP 支持的脚本语言俱乐部。 正如 horseguy 在之前的评论中已经提到的,在“Ruby 中的实例变量始终是私有的”语句中,私有的意思是“不能在类外使用 . 直接访问”。但是,您可以使用 instance_variable_get() 方法从类外部访问属性,子类可以访问这些属性。在 OOP(和 C++)术语中,Ruby 中的属性将受到保护(如果您忽略 instance_variable_get() 访问器)或公共(如果您不这样做)。 用了一段时间,现在分享..gist.github.com/amolpujari/ad1f4b61a3ffc50ab4e90dfe9b0dbac1【参考方案2】:

永远不要直接使用实例变量。只使用访问器。您可以通过以下方式将阅读器定义为公共的,将作者定义为私有的:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

但是,请记住,privateprotected 并不是您认为的意思。可以针对任何接收者调用公共方法:命名、自我或隐式(x.bazself.bazbaz)。受保护的方法只能使用 self 的接收者或隐式调用(@​​987654327@、baz)。私有方法只能通过隐式接收者 (baz) 调用。

长话短说,您是从非 Ruby 的角度来处理问题的。始终使用访问器而不是实例变量。使用 public/protected/private 记录您的意图,并假设您的 API 的使用者是负责任的成年人。

【讨论】:

关于可访问性和接收器的部分确实有助于澄清我过去遇到的一些问题。 "永远不要直接使用实例变量..." 为什么不呢?它们是语言的核心部分。我会说这取决于您的情况和您要解决的问题。 这是一个经验法则。当然attr_readerattr_writer 在后台使用实例变量。您可能希望直接将它们用于透明记忆 (@_foo ||= begin; # slow operation; end)。但是,如果您直接使用实例变量,则在获取或设置它们的值时不能在不更改其他任何地方的代码(包括子类化它们的代码)的情况下挂钩它们的行为。如果您拼错了@isntance_variable,而您拼错了self.mtehod(),也不会出现异常。它们并不比@@class_variables 更“中心”,后者同样被禁止。【参考方案3】:

可以(但不建议)完全按照您的要求进行操作。

所需行为有两个不同的元素。第一个是将x存储在一个只读值中,第二个是保护getter不被子类改变。


只读值

在 Ruby 中可以在初始化时存储只读值。为此,我们使用 Ruby 块的闭包行为。

class Foo
  def initialize (x)
    define_singleton_method(:x)  x 
  end
end

x 的初始值现在被锁定在我们用来定义 getter #x 的块内,除非通过调用 foo.x,否则永远无法访问,并且永远无法更改。

foo = Foo.new(2)
foo.x  # => 2
foo.instance_variable_get(:@x)  # => nil

请注意,它并未存储为实例变量@x,但仍可通过我们使用define_singleton_method 创建的getter 获得。


保护吸气剂

在 Ruby 中,几乎任何类的任何方法都可以在运行时被覆盖。有一种方法可以使用method_added 挂钩来防止这种情况发生。

class Foo
  def self.method_added (name)
    raise(NameError, "cannot change x getter") if name == :x
  end
end

class Bar < Foo
  def x
    20
  end
end

# => NameError: cannot change x getter

这是一种非常严厉的保护getter的方法。

它要求我们将每个受保护的 getter 单独添加到 method_added 钩子中,即使这样,您也需要向 Foo 及其子类添加另一个级别的 method_added 保护,以防止编码器覆盖 @ 987654335@ 方法本身。

最好接受这样一个事实,即在运行时替换代码是使用 Ruby 时的生活事实。

【讨论】:

请记住,定义方法将使 ruby​​ 的方法缓存无效。如果您要创建大量此类,可能会对性能产生不利影响。 @Kelvin,这是一个非常好的观点,谢谢。任何有兴趣了解更多关于 Ruby 中这种性能损失的人都应该查看这篇精彩的文章:github.com/charliesome/charlie.bz/blob/master/posts/…【参考方案4】:

与具有不同可见性级别的方法不同,Ruby 实例变量始终是私有的(来自对象外部)。但是,内部对象的实例变量始终可以从父类、子类或包含的模块访问。

由于可能无法更改 Ruby 访问@x 的方式,我认为您无法控制它。编写 @x 只会直接选择该实例变量,并且由于 Ruby 不提供对变量的可见性控制,所以我猜是这样。

正如@marcgg 所说,如果您不希望派生类接触您的实例变量,则根本不要使用它,或者找到一种巧妙的方法来隐藏它以防止派生类看到。

【讨论】:

【参考方案5】:

不可能做你想做的事,因为实例变量不是由类定义的,而是由对象定义的。

如果您使用组合而不是继承,那么您就不必担心会覆盖实例变量。

【讨论】:

+1。在大多数情况下,组合提供了更灵活的解决方案。如果派生类不能访问私有成员变量以防止开发人员意外重用变量名的情况会很好,但是无论如何,在 ruby​​ 中不需要变量预声明。 Andrew 的第一句话是如此真实,以至于来自 Java/C++ 的程序员应该在他们的手上纹身!类不“声明”实例变量。实例变量在程序执行时被添加到对象中。如果创建实例变量的方法未被调用,则对象将永远不会拥有该实例变量。【参考方案6】:

我知道这是旧的,但我遇到了一个情况,我不想阻止对 @x 的访问,我确实想从任何使用反射进行序列化的方法中排除它。具体来说,我经常将YAML::dump 用于调试目的,在我的情况下,@x 属于Class 类,YAML::dump 拒绝转储。

在这种情况下,我考虑了几个选项

    通过重新定义“to_yaml_properties”来解决这个问题

    def to_yaml_properties
      super-["@x"]
    end
    

    但这仅适用于 yaml,如果其他倾销者 (to_xml ?) 不高兴

    通过重新定义“instance_variables”为所有反射用户寻址

    def instance_variables
      super-["@x"]
    end
    

    另外,我在一次搜索中找到了this,但尚未对其进行测试,因为上述内容似乎更适合我的需求

因此,虽然这些可能不是 OP 所说的他所需要的,但如果其他人在寻找要从列表中排除的变量而不是访问时发现此帖子 - 那么这些选项可能是有价值的。

【讨论】:

我建议将此作为一个单独的问题提出并自己回答。在这里回答会产生额外的噪音。 @Kelvin 我在这里回答是因为不清楚为什么 OP 想要这样做,但如果他的原因与我的相似,这会对他有所帮助。他从来没有说出他的理由,如果他这样做并且他的全部目标不同,那么我会删除它。因为它会帮助任何试图解决特定用例的人解决这个问题。我认为我不应该问一个我已经知道答案的问题(回答自己的问题显然很好)

以上是关于如何在 Ruby 中将实例变量设为私有?的主要内容,如果未能解决你的问题,请参考以下文章

Ruby / IRB:将实例变量设置为私有或不可见?

Ruby的attr_accessor如何产生类变量或类实例变量而不是实例变量?

如何在 Obj-C 或 Swift 中访问私有实例变量?

如何从类内实例化的对象访问私有变量

Ruby 元编程:动态实例变量名

JAVA类与对象---实例变量与类变量的区别,实例方法和类方法的区别