有没有办法在 ruby 中使实例变量“私有”(C++ 或 Java 定义)?换句话说,我希望以下代码导致错误。
class Base
def initialize()
@x = 10
end
end
class Derived < Base
def x
@x = 20
end
end
d = Derived.new
有没有办法在 ruby 中使实例变量“私有”(C++ 或 Java 定义)?换句话说,我希望以下代码导致错误。
class Base
def initialize()
@x = 10
end
end
class Derived < Base
def x
@x = 20
end
end
d = Derived.new
像 Ruby 中的大多数东西一样,实例变量并不是真正的“私有”,任何拥有d.instance_variable_get :@x
.
然而,与 Java/C++ 不同的是,Ruby 中的实例变量始终是私有的。它们永远不会像方法那样成为公共 API 的一部分,因为它们只能用那个冗长的 getter 来访问。因此,如果您的 API 有任何合理性,您不必担心有人滥用您的实例变量,因为他们将使用这些方法。(当然,如果有人想疯狂访问私有方法或实例变量,没有办法阻止他们。)
唯一需要担心的是,如果有人在扩展您的类时意外覆盖了实例变量。这可以通过使用不太可能的名称来避免,也许@base_x
在您的示例中调用它。
永远不要直接使用实例变量。只使用访问器。您可以通过以下方式将阅读器定义为公共的,将作者定义为私有的:
class Foo
attr_reader :bar
private
attr_writer :bar
end
但是,请记住这一点,private
而protected
不是您认为的意思。可以针对任何接收者调用公共方法:命名、自我或隐式(x.baz
、、self.baz
或baz
)。受保护的方法只能用 self 的接收者或隐式 ( self.baz
, baz
) 调用。私有方法只能通过隐式接收器 ( baz
) 调用。
长话短说,您是从非 Ruby 的角度来处理问题的。始终使用访问器而不是实例变量。使用public
//来protected
记录private
您的意图,并假设您的 API 的使用者是负责任的成年人。
可以(但不建议)完全按照您的要求进行操作。
所需行为有两个不同的元素。第一个是存储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
,但它仍然可以通过我们使用创建的 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
这是保护 getter 的一种非常严厉的方法。
它要求我们将每个受保护的 gettermethod_added
单独添加到钩子中,即使这样,您也需要为其及其子类添加另一层method_added
保护,以防止编码器覆盖方法本身。Foo
method_added
最好接受这样一个事实,即在运行时替换代码是使用 Ruby 时的生活事实。
与具有不同可见性级别的方法不同,Ruby 实例变量始终是私有的(来自对象外部)。但是,内部对象的实例变量始终可以从父类、子类或包含的模块访问。
由于可能无法更改 Ruby 的访问方式@x
,我认为您无法控制它。写作@x
只会直接选择该实例变量,并且由于 Ruby 不提供对变量的可见性控制,所以我猜可以使用它。
正如@marcgg 所说,如果您不希望派生类接触您的实例变量,请根本不要使用它,或者找到一种巧妙的方法来隐藏它,使其不被派生类看到。
不可能做你想做的事,因为实例变量不是由类定义的,而是由对象定义的。
如果您使用组合而不是继承,那么您将不必担心覆盖实例变量。
我知道这很旧,但是我遇到了一个情况,我不想阻止对@x 的访问,我确实想将它从任何使用反射进行序列化的方法中排除。具体来说,我YAML::dump
经常将其用于调试目的,在我的情况下,@x 属于 class Class
,它YAML::dump
拒绝转储。
在这种情况下,我考虑了几个选项
通过重新定义“to_yaml_properties”来解决这个问题
def to_yaml_properties
super-["@x"]
end
但这仅适用于 yaml 并且如果其他倾销to_xml
者(?)不高兴
通过重新定义“instance_variables”为所有反射用户寻址
def instance_variables
super-["@x"]
end
另外,我在我的一次搜索中发现了这个,但没有测试它,因为上面看起来更适合我的需要
因此,虽然这些可能不是 OP 所说的他所需要的,但如果其他人在寻找要从列表中排除的变量而不是访问时发现此帖子,那么这些选项可能是有价值的。