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

发布于 2024-08-18 16:23:22 字数 215 浏览 7 评论 0原文

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

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

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

d = Derived.new

Is there any way to make instance variables "private"(C++ or Java definition) in ruby? In other words I want following code to result in an error.

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

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

d = Derived.new

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

歌枕肩 2024-08-25 16:23:22

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

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

唯一的问题是,如果有人在扩展时不小心覆盖了实例变量你的班级。可以通过使用不太可能的名称来避免这种情况,也许在您的示例中将其称为 @base_x

Like most things in Ruby, instance variables aren't truly "private" and can be accessed by anyone with d.instance_variable_get :@x.

Unlike in Java/C++, though, instance variables in Ruby are always private. They are never part of the public API like methods are, since they can only be accessed with that verbose getter. So if there's any sanity in your API, you don't have to worry about someone abusing your instance variables, since they'll be using the methods instead. (Of course, if someone wants to go wild and access private methods or instance variables, there isn’t a way to stop them.)

The only concern is if someone accidentally overwrites an instance variable when they extend your class. That can be avoided by using unlikely names, perhaps calling it @base_x in your example.

一百个冬季 2024-08-25 16:23:22

切勿直接使用实例变量。只使用访问器。您可以通过以下方式将读取器定义为公共,将写入器定义为私有:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

但是,请记住,privateprotected 的含义并不像您所认为的那样。可以针对任何接收者调用公共方法:命名、自身或隐式(x.bazself.bazbaz)。受保护的方法只能通过 self 接收者或隐式调用(self.bazbaz)。私有方法只能用隐式接收者 (baz) 调用。

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

Never use instance variables directly. Only ever use accessors. You can define the reader as public and the writer private by:

class Foo
  attr_reader :bar

  private

  attr_writer :bar
end

However, keep in mind that private and protected do not mean what you think they mean. Public methods can be called against any receiver: named, self, or implicit (x.baz, self.baz, or baz). Protected methods may only be called with a receiver of self or implicitly (self.baz, baz). Private methods may only be called with an implicit receiver (baz).

Long story short, you're approaching the problem from a non-Ruby point of view. Always use accessors instead of instance variables. Use public/protected/private to document your intent, and assume consumers of your API are responsible adults.

何必那么矫情 2024-08-25 16:23:22

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

所需行为有两个不同的要素。第一个是将 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 来使用它。


保护 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 钩子,即使这样,您也需要向 Foo 添加另一级别的 method_added 保护> 及其子类,以防止编码器覆盖 method_added 方法本身。

使用 Ruby 时,最好接受这样一个事实:运行时代码替换是不可避免的。

It is possible (but inadvisable) to do exactly what you are asking.

There are two different elements of the desired behavior. The first is storing x in a read-only value, and the second is protecting the getter from being altered in subclasses.


Read-only value

It is possible in Ruby to store read-only values at initialization time. To do this, we use the closure behavior of Ruby blocks.

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

The initial value of x is now locked up inside the block we used to define the getter #x and can never be accessed except by calling foo.x, and it can never be altered.

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

Note that it is not stored as the instance variable @x, yet it is still available via the getter we created using define_singleton_method.


Protecting the getter

In Ruby, almost any method of any class can be overwritten at runtime. There is a way to prevent this using the method_added hook.

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

This is a very heavy-handed method of protecting the getter.

It requires that we add each protected getter to the method_added hook individually, and even then, you will need to add another level of method_added protection to Foo and its subclasses to prevent a coder from overwriting the method_added method itself.

Better to come to terms with the fact that code replacement at runtime is a fact of life when using Ruby.

醉酒的小男人 2024-08-25 16:23:22

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

由于可能无法改变 Ruby 访问 @x 的方式,因此我认为您无法对其进行任何控制。编写 @x 只会直接选择该实例变量,并且由于 Ruby 不提供对变量的可见性控制,我想就接受它吧。

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

Unlike methods having different levels of visibility, Ruby instance variables are always private (from outside of objects). However, inside objects instance variables are always accessible, either from parent, child class, or included modules.

Since there probably is no way to alter how Ruby access @x, I don't think you could have any control over it. Writing @x would just directly pick that instance variable, and since Ruby doesn't provide visibility control over variables, live with it I guess.

As @marcgg says, if you don't want derived classes to touch your instance variables, don't use it at all or find a clever way to hide it from seeing by derived classes.

可是我不能没有你 2024-08-25 16:23:22

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

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

It isn't possible to do what you want, because instance variables aren't defined by the class, but by the object.

If you use composition rather than inheritance, then you won't have to worry about overwriting instance variables.

╰◇生如夏花灿烂 2024-08-25 16:23:22

如果您想防止意外修改。我认为 attr_accessor 非常适合。

class Data
 attr_accessor :id
 
 private :id
end

这将禁用 id 的写入,但仍可读。不过,您也可以使用公共 attr_reader 和私有 attr_writer 语法。就像这样:

class Data
 attr_reader :id
 
 private
 
 attr_writer :id
end

If you want protection against accidental modification. I think attr_accessor can be a good fit.

class Data
 attr_accessor :id
 
 private :id
end

That will disable writing of id but would be readable. You can however use public attr_reader and private attr_writer syntax as well. Like so:

class Data
 attr_reader :id
 
 private
 
 attr_writer :id
end
梦冥 2024-08-25 16:23:22

我知道这已经很旧了,但我遇到了一种情况,我并不想阻止对 @x 的访问,我确实想将其从任何使用反射进行序列化的方法中排除。具体来说,我经常使用 YAML::dump 进行调试,在我的例子中,@x 属于 Class 类,而 YAML::dump 拒绝它倾倒。

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

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

    def to_yaml_properties
      超级[“@x”]
    结尾
    

    但这仅适用于 yaml,并且如果其他转储器(to_xml?)不高兴

  2. 通过重新定义“instance_variables”来解决所有反射用户的问题

    def 实例变量
      超级[“@x”]
    结尾
    
  3. 此外,我发现这个< /a> 在我的一次搜索中,但尚未测试它,因为上面的内容似乎更适合我的需求

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

I know this is old, but I ran into a case where I didn't as much want to prevent access to @x, I did want to exclude it from any methods that use reflection for serialization. Specifically I use YAML::dump often for debug purposes, and in my case @x was of class Class, which YAML::dump refuses to dump.

In this case I had considered several options

  1. Addressing this just for yaml by redefining "to_yaml_properties"

    def to_yaml_properties
      super-["@x"]
    end
    

    but this would have worked just for yaml and if other dumpers (to_xml ?) would not be happy

  2. Addressing for all reflection users by redefining "instance_variables"

    def instance_variables
      super-["@x"]
    end
    
  3. Also, I found this in one of my searches, but have not tested it as the above seem simpler for my needs

So while these may not be exactly what the OP said he needed, if others find this posting while looking for the variable to be excluded from listing, rather than access - then these options may be of value.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文