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

时间:2010-01-25 11:34:46

标签: ruby instance-variables private-members

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

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

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

d = Derived.new

6 个答案:

答案 0 :(得分:34)

与Ruby中的大多数内容一样,实例变量并非真正“私有”,只有d.instance_variable_get :@x的任何人都可以访问。

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

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

答案 1 :(得分:26)

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

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

但请注意,privateprotected并不代表您认为的含义。可以针对任何接收者调用公共方法:命名,自我或隐式(x.bazself.bazbaz)。受保护的方法只能通过自我或隐式接收器调用(self.bazbaz)。私有方法只能使用隐式接收器(baz)来调用。

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

答案 2 :(得分: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时运行时代码替换是事实的事实。

答案 3 :(得分:5)

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

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

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:-1)

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

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

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

    def to_yaml_properties
      super-["@x"]
    end
    

    但这只适用于yaml,如果其他翻斗车(to_xml?)不满意

  2. 通过重新定义“instance_variables”

    来寻址所有反射用户
    def instance_variables
      super-["@x"]
    end
    
  3. 此外,我在我的某个搜索中找到了this,但未对其进行测试,因为上述内容似乎更符合我的需求

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