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

pra*_*dvk 34 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
Run Code Online (Sandbox Code Playgroud)

Jos*_*Lee 35

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

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

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

  • 这不是问题,在他的代码中,他可以修改派生类中的`@ x`变量?这与C++中的内容相反,其中派生类_cannot_访问私有数据成员.因此,虽然"ruby中的实例变量是私有的"确实如此 - 重要的一点是,它与C++中private的含义不同. (11认同)
  • 我认为用C++的说法,人们会说'ruby中的实例变量总是受到保护'.虽然不是一个完美的模拟,但它比私有的C++含义更准确. (8认同)

Ste*_*set 26

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

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end
Run Code Online (Sandbox Code Playgroud)

但是,请记住,privateprotected并不意味着什么,你认为他们的意思.公共方法可以称为对任何接收器:命名,自我,或隐式的(x.baz,self.baz,或baz).保护的方法可仅具有自的接收器或隐式地(称为self.baz,baz).私有方法只能使用隐式receiver(baz)调用.

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

  • “永远不要直接使用实例变量......”为什么不呢?它们是语言的核心部分。我想说这取决于你的情况和你想要解决的问题。 (3认同)

use*_*951 13

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

期望行为有两个不同的元素.第一个是以只读值存储x,第二个是保护getter不被子类更改.


只读值

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

class Foo
  def initialize (x)
    define_singleton_method(:x) { x }
  end
end
Run Code Online (Sandbox Code Playgroud)

现在,初始值x被锁定在我们用来定义getter的块中,#x除了通过调用之外永远不会被访问foo.x,并且它永远不会被改变.

foo = Foo.new(2)
foo.x  # => 2
foo.instance_variable_get(:@x)  # => nil
Run Code Online (Sandbox Code Playgroud)

请注意,它不是作为实例变量存储的@x,但它仍然可以通过我们创建的getter使用define_singleton_method.


保护吸气剂

在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
Run Code Online (Sandbox Code Playgroud)

这是一种非常严厉的保护吸气剂的方法.

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

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

  • 请记住,定义方法将使ruby的方法缓存无效.如果你创造了很多这些,它可能会对性能产生负面影响. (2认同)

bry*_*sai 5

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

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

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