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

问题描述 投票:33回答:6

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

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

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

d = Derived.new
ruby instance-variables private-members
6个回答
35
投票

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

但是,与Java / C ++不同,Ruby中的实例变量始终是私有的。它们永远不会像方法那样属于公共API,因为只能使用那个详细的getter来访问它们。因此,如果您的API中有任何理智,您不必担心有人滥用您的实例变量,因为他们将使用这些方法。 (当然,如果有人想要疯狂并访问私有方法或实例变量,则无法阻止它们。)

唯一的问题是,如果有人在扩展您的课程时意外覆盖了实例变量。这可以通过使用不太可能的名称来避免,也许在你的例子中称之为@base_x


26
投票

切勿直接使用实例变量。只使用访问者。您可以通过以下方式将阅读器定义为public和writer私有:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

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

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


13
投票

有可能(但不建议)完全按照你的要求去做。

期望行为有两个不同的元素。第一个是将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单独添加到method_added钩子中,即使这样,您还需要为method_added及其子类添加另一级别的Foo保护,以防止编码器覆盖method_added方法本身。

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


5
投票

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

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

正如@marcgg所说,如果您不希望派生类触及您的实例变量,请不要使用它或找到一种聪明的方法来隐藏它不被派生类看到。


1
投票

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

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


-1
投票

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

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

  1. 通过重新定义“to_yaml_properties”为yaml解决这个问题 def to_yaml_properties super-["@x"] end 但是如果其他翻斗车(to_xml?)不开心的话,这只会对yaml有效
  2. 通过重新定义“instance_variables”来解决所有反射用户 def instance_variables super-["@x"] end
  3. 此外,我在我的一个搜索中找到了this,但没有测试它,因为上面的内容似乎更符合我的需求

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

© www.soinside.com 2019 - 2024. All rights reserved.