英文:
Is there a difference between protected class methods and private class methods in Ruby?
问题
私有类方法和受保护的类方法在Ruby中在可访问性方面的区别是什么?提供了一个用于研究区别的代码示例:
迄今为止,我得出以下结论:
- 两者都可以从类内部访问,例如
self.accessprivatemethodfromwithinsameclass
和self.accessprotectedmethodfromwithinsameclass
。 - 两者都无法从类外部直接访问,例如
Aclass.protectedmethod
和Aclass.privatemethod
。 - 两者都可以从子类内部访问,例如
access...methodwithinsubclass
。 - 但是,两者都不能在子类内部直接调用,例如
ASubclass.private/protectedmethod
。
行为上唯一的区别在于当它们无法访问时,受保护的方法会显示以下错误消息:
NoMethodError: protected method ... called for ...Class
而私有方法会显示以下错误消息:
NoMethodError: private method ... called for ...Class
我的问题是:是否存在在行为上有差异的情况?
英文:
I'm trying to understand the difference between private class methods and protected class methods in Ruby in terms of accessibility. I've provided a code example that I am using to study the difference:
class Aclass
#public methods below
def self.accessprivatemethodfromwithinsameclass
privatemethod
end
def self.accessprotectedmethodfromwithinsameclass
protectedmethod
end
class << self
private
def privatemethod
"I'm a private method"
end
protected
def protectedmethod
"I'm a protected method"
end
end
end
class ASubClass < Aclass
class << self
def accessprivatemethodfromwithinsubclass
privatemethod
end
def accessprotectedmethodfromwithinsubclass
protectedmethod
end
end
end
#Example
Aclass.protectedmethod
Aclass.privatemethod
Aclass.accessprivatemethodfromwithinsameclass
Aclass.accessprotectedmethodfromwithinsameclass
ASubClass.accessprivatemethodfromwithinsubclass
ASubClass.accessprotectedmethodfromwithinsubclass
ASubClass.privatemethod
ASubClass.protectedmethod
So far I've concluded that:
- Both are accessible from within the class ie. as in
self.accessprivatemethodfromwitinsameclass
andself.accessprotectedfromwithinsameclass
- Both are not accessible from outside the class as in
Aclass.protectedmethod
andAclass.privatemethod
. - Both are accessible from within the subclass see
access...methodwithinsubclass
. - However both cannot be directly called within the subclass. see
ASubclass.private/protectedmethod
.
The only difference in behaviour is that when they are not accessible the protected method says:
> NoMethodError: protected method ... called for ...Class
where the private method says:
> NoMethodError: private method ... called for ...Class.
My question is as follows:
is there ever a case where there is a difference in behavior?
答案1
得分: 4
以下是翻译好的部分:
Ruby中没有类方法
首先要理解的一个重要概念是,在Ruby对象模型中不存在类方法这种东西。我们在日常交流中谈论类方法,甚至Ruby核心库中有一些引用类方法的方法(例如Module#public_class_method
和Module#private_class_method
),但从根本上来说,在对象模型中,它们并不存在。
我们所谓的类方法实际上只是一个普通的单例方法,就像任何对象都可以有的方法一样。只是这个对象恰好是Class
类的一个实例。但从根本上讲,Foo
的实例和Class
的实例之间没有任何区别。
之所以我们为后者使用特殊名称,是因为它们非常常见,而且“一个恰好是Class
实例的对象的单例方法”这个说法太长了,所以我们称之为“类方法”。
Ruby中没有单例方法
第二个重要概念是,在Ruby对象模型中也没有单例方法这种东西!我们在日常交流中谈论单例方法,甚至Ruby核心库中有一些引用单例方法的方法(例如Object#singleton_method
、Object#singleton_methods
或Object#define_singleton_method
),但从根本上来说,在对象模型中,它们并不存在。
我们所谓的单例方法实际上只是单例类的普通实例方法。但这只是一个普通的实例方法,没有什么特别之处。单例类才是特殊的,而不是方法。但从根本上讲,在任何其他模块中定义的实例方法和在单例类中定义的单例方法之间没有区别。
之所以我们为后者使用特殊名称,是因为区分它们很有用,而且“一个恰好是单例类的模块的实例方法”这个说法太长了,所以我们称之为“单例方法”。
同样,“一个恰好是Class
的实例的单例类的模块的实例方法”这个说法也很长,所以我们称之为“类方法”。
Ruby中的消息可访问性
然而,现在我们知道类方法只是单例方法,而单例方法只是实例方法(实际上,在Ruby中只有一种方法,即实例方法,没有单例方法、类方法、静态方法或构造函数),我们可以简单地看一下Ruby中的消息可访问性,因为我们知道类方法没有什么特别之处。
Ruby有三个消息可访问性级别:
public
(默认级别)private
protected
附注:这是在哪里定义的?
不幸的是,与许多其他编程语言不同,Ruby语言规范不存在于一个单一的文档中。Ruby没有一个单一的正式规范来定义某些语言构造的含义。
有几个资源,它们的总和可以被视为Ruby编程语言的规范。
其中一些资源包括:
- ISO/IEC 30170:2012 信息技术 —— 编程语言 —— Ruby 规范 - 请注意,ISO Ruby规范是在2009年至2010年左右编写的,其特定目标是确保当时所有现有的Ruby实现都容易符合。由于YARV和MacRuby只实现了Ruby 1.9+,MRI只实现了Ruby 1.8及更低版本,而JRuby、XRuby、Ruby.NET和IronRuby(当时)只实现了Ruby 1.8的一个子集,这意味着ISO Ruby规范只包含对Ruby 1.8和Ruby 1.9都通用的功能。此外,ISO Ruby规范旨在保持最小,并仅包含编写Ruby程序所绝对需要的功能。因此,它对
String
的规范非常广泛(因为它们在Ruby 1.8和Ruby 1.9之间发生了重大变化)。显然,它也没有规定在ISO Ruby规范编写之后添加的功能,比如Ractors或Pattern Matching。 - Ruby规范套件,即
ruby/spec
- 但遗憾的是,ruby/spec
远未完成。然而,我非常喜欢它,因为它是用Ruby编写的,而不是“ISO标准语言”,对于Ruby程序员来说,这样的编写方式更容易阅读,而且它还可以作为可执行的符合性测试套件。 - 由David Flanagan和Yukihiro 'matz' Matsumoto编写的Ruby编程语言 - 这本书是由David Flanagan与Ruby的创始人matz共同编写的,旨在作为Ruby的语言参考。
- 由Dave Thomas、Andy Hunt和Chad Fowler编写的Programming Ruby - 这
英文:
There are no class methods in Ruby
The first important concept to understand is that there is no such thing as a class method in the Ruby Object Model. We talk about class methods colloquially, and there are even methods in the Ruby core library which refer to class methods (e.g. Module#public_class_method
and Module#private_class_method
), but fundamentally, in the Object Model, they don't exist.
What we call a class method is actually just a normal singleton method like any object can have it. It's just that the object happens to be an instance of the class Class
. But fundamentally, there is no difference between a singleton method of an object that is an instance of Foo
and an object that is an instance of Class
.
The reason we use a special name for the latter is that they are very common, and "singleton method of an object that happens to be an instance of Class
" is a mouthful, so we say "class method" instead.
There are no singleton methods in Ruby
The second important concept to understand is that there is no such thing as a singleton method in the Ruby Object Model either! We talk about singleton methods colloquially, and there are even methods in the Ruby core library which refer to singleton methods (e.g. Object#singleton_method
, Object#singleton_methods
, or Object#define_singleton_method
), but fundamentally, in the Object Model, they don't exist.
What we call a singleton method is actually just a normal instance method of the singleton class. But it's a bog standard instance method, there is nothing special about it. It's the singleton class that is special, not the method. But fundamentally, there is no difference between an instance method that is defined in any other module and a singleton method that is defined in a singleton class.
The reason we use a special name for the latter is that it is useful to distinguish them, and "instance method of a module that happens to be a singleton class" is a mouthful, so we say "singleton method" instead.
Likewise, "instance method of a module that happens to be a singleton class of an object that happens to be an instance of Class
" is a mouthful, and so way say "class method" instead.
Message accessibility in Ruby
However, since we now know that class method are just singleton methods and singleton methods are just instance methods (and in fact, there is only one kind of method in Ruby, instance methods, no singleton methods, no class methods, no static methods, no constructors), we can simply look at message accessibility in Ruby in general, since we know that there is nothing special about class methods.
Ruby has three message accessibility levels:
public
(the default)private
protected
Aside: where is this defined?
Unfortunately, unlike many other programming languages, the Ruby Language Specification does not exist as a single document in a single place. Ruby does not have a single formal specification that defines what certain language constructs mean.
There are several resources, the sum of which can be considered kind of a specification for the Ruby programming language.
Some of these resources are:
- The ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification – Note that the ISO Ruby Specification was written around 2009–2010 with the specific goal that all existing Ruby implementations at the time would easily be compliant. Since YARV and MacRuby only implement Ruby 1.9+ and MRI only implements Ruby 1.8 and lower and JRuby, XRuby, Ruby.NET, and IronRuby (at the time) only implemented a subset of Ruby 1.8, this means that the ISO Ruby Specification only contains features that are common to both Ruby 1.8 and Ruby 1.9. Also, the ISO Ruby Specification was specifically intended to be minimal and only contain the features that are absolutely required for writing Ruby programs. Because of that, it does for example only specify
String
s very broadly (since they have changed significantly between Ruby 1.8 and Ruby 1.9). It obviously also does not specify features which were added after the ISO Ruby Specification was written, such as Ractors or Pattern Matching. - The Ruby Spec Suite aka
ruby/spec
– Note that theruby/spec
is unfortunately far from complete. However, I quite like it because it is written in Ruby instead of "ISO-standardese", which is much easier to read for a Rubyist, and it doubles as an executable conformance test suite. - The Ruby Programming Language by David Flanagan and Yukihiro 'matz' Matsumoto – This book was written by David Flanagan together with Ruby's creator matz to serve as a Language Reference for Ruby.
- Programming Ruby by Dave Thomas, Andy Hunt, and Chad Fowler – This book was the first English book about Ruby and served as the standard introduction and description of Ruby for a long time. This book also first documented the Ruby core library and standard library, and the authors donated that documentation back to the community.
- The Ruby Issue Tracking System, specifically, the Feature sub-tracker – However, please note that unfortunately, the community is really, really bad at distinguishing between Tickets about the Ruby Programming Language and Tickets about the YARV Ruby Implementation: they both get intermingled in the tracker.
- The Meeting Logs of the Ruby Developer Meetings. (Same problem: Ruby and YARV get intermingled.)
- New features are often discussed on the mailing lists, in particular the ruby-core (English) and ruby-dev (Japanese) mailing lists. (Same problem again.)
- The Ruby documentation – Again, be aware that this documentation is generated from the source code of YARV and does not distinguish between features of Ruby and features of YARV.
- In the past, there were a couple of attempts of formalizing changes to the Ruby Specification, such as the Ruby Change Request (RCR) and Ruby Enhancement Proposal (REP) processes, both of which were unsuccessful.
- If all else fails, you need to check the source code of the popular Ruby implementations to see what they actually do. Please note the plural: you have to look at multiple, ideally all, implementations to figure out what the consensus is. Only looking at one implementation cannot possibly tell you whether what you are looking at is an implementation quirk of this particular implementation or is a universally agreed-upon behavior of the Ruby Language.
public
public
is easy: it has no restrictions. Done.
private
The rule for private
accessibility is also very simple: a message send can only invoke a private method if
- the message send has an implicit receiver (
some_private_method
) OR - the explicit receiver is the literal pseudo-variable keyword
self
(self.some_private_method
).
It is not enough that the receiver is self
! For example, this is not allowed:
foo = self
foo.some_private_method
The receiver must be either implicit or it must be the literal pseudo-variable keyword self
.
The reason for this restriction is that the implementors of Ruby implementations with highly-sophisticated, aggressively optimizing compilers wanted to be able to statically determine whether a message send was allowed to invoke a private method or not. If you relax this rule to say "a private method can be invoked if the receiver is self
", then you can no longer statically determine this, see the following example:
foo = if rand < 0.5 then self else some_other_object end
foo.some_private_method
This is what section 13.3.5.3 Private methods of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification has to say:
> # 13.3.5.3 Private methods
> A private method is a method whose visibility attribute is set to the private visibility.
>
> A private method cannot be invoked with an explicit receiver, i.e., a private method cannot be invoked if a primary-expression or a chained-method-invocation occurs at the position which corresponds to the method receiver in the method invocation, except for a method invocation of any of the following forms where the primary-expression is a self-expression.
>
> * single-method-assignment
> * abbreviated-method-assignment
> * single-indexing-assignment
> * abbreviated-indexing-assignment
Note that the ISO Ruby Programming Language Specification has not been updated since 2012, so this is a slightly outdated definition. The specification was slightly simplified a couple of years ago. Nowadays, the second paragraph should probably read more like this:
> A private method cannot be invoked with an explicit receiver, i.e., a private method cannot be invoked if a primary-expression or a chained-method-invocation occurs at the position which corresponds to the method receiver in the method invocation, except for a method invocation where the primary-expression is a self-expression.
But note that this doesn't make a difference here, since in your code you are not using a self-expression as the receiver anyway, so whether a self-expression is allowed in a limited number of cases or always is irrelevant.
The ruby/spec for private
and for message sends also does not seem to be fully up-to-date, but again, the differences do not matter for your question.
The RDoc for Modules, specifically, the subsection on Visibility, however, is fully up-to-date and pretty clear:
> The third visibility is private. A private method may only be called from inside the owner class without a receiver, or with a literal self
as a receiver. If a private method is called with a receiver other than a literal self
, a NoMethodError
will be raised.
protected
In order to be allowed to invoke a protected
method, the sender needs to be an instance of the module the invoked method is defined in.
In other words, if you have a protected
method m
defined in module M
like this:
module M
protected def m; end
end
Then m
can only be invoked by a message send where the sender is an instance of M
:
kind_of?(M) #=> true
M === self #=> true
receiver.m
So, in other words, in order for
some_object.some_protected_method
to succeed,
kind_of?(some_object.method(:some_protected_method).owner)
or
some_object.method(:some_protected_method).owner === self
must be true
.
[Note: I am assuming that Object#method
, Method#owner
, Module#===
, and Object#kind_of?
have not been monkey patched or overridden. The check is not guaranteed to be performed using those actual methods, it is typically performed using internal knowledge by the implementation. In other words, you can't override the behavior of protected
by monkey patching or overriding those methods.]
Here's what section 13.3.5.4 Protected methods of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification has to say:
> # 13.3.5.4 Protected methods
> A protected method is a method whose visibility attribute is set to the protected visibility.
>
> A protected method can be invoked if and only if the following condition holds:
>
> * Let M be an instance of the class Module
in which the binding of the method exists.
> M is included in the current self, or M is the class of the current self or one of its superclasses.
>
> If M is a singleton class, whether the method can be invoked or not may be determined in an implementation-defined way.
And the RDoc:
> The second visibility is protected
. When calling a protected method the sender must inherit the Class
or Module
which defines the method. Otherwise a NoMethodError
will be raised.
>
> Protected visibility is most frequently used to define ==
and other comparison methods where the author does not wish to expose an object’s state to any caller and would like to restrict it only to inherited classes.
Your examples
Now, let's apply what we learned above to your examples:
> So far I've concluded that:
> - Both are accessible from within the class ie. as in self.accessprivatemethodfromwitinsameclass
and self.accessprotectedfromwithinsameclass
The classes are irrelevant here. All that matters is the sender and receiver (for protected
) or just the receiver (for private
).
privatemethod
works, not because it is "within the same class", but because the receiver is implicit.
protectedmethod
works, not because it is "within the same class", but because the sender and the receiver are both the same object (the sender is always self
, the receiver in this case is implicit, and the implicit receiver is always self
), so it is trivially true that the sender is an instance of the receiver's type.
> - Both are not accessible from outside the class as in Aclass.protectedmethod
and Aclass.privatemethod
.
Again, the classes don't really matter here.
Aclass.privatemethod
doesn't work, not because it is "outside the class", but because the receiver is neither implicit (i.e. privatemethod
) nor the literal pseudo-variable keyword self
(i.e. self.privatemethod
).
Aclass.protectedmethod
doesn't work, not because it is "outside the class", but because the sender (in this case the un-named top-level object typically called main
, which is a direct instance of Object
) is not an instance of the module the method is defined in (in this case Aclass.singleton_class
).
> - Both are accessible from within the subclass see access...methodwithinsubclass
.
Again, the subclass is irrelevant here. In fact, these two cases are exactly identical to the first and second cases from your first bullet point.
privatemethod
works, not because it is "within the subclass", but because the receiver is implicit.
protectedmethod
works, not because it is "within the subclass", but because the sender and the receiver are both the same object (the sender is always self
, the receiver in this case is implicit, and the implicit receiver is always self
), so it is trivially true that the sender is an instance of the receiver's type.
> - However both cannot be directly called within the subclass. see ASubclass.private/protectedmethod
.
Again, the subclass is not really relevant here. In fact, these two cases are exactly identical to the third and fourth cases from your second bullet point before.
ASubclass.privatemethod
doesn't work, not because it is "outside the class", but because the receiver is neither implicit (i.e. privatemethod
) nor the literal pseudo-variable keyword self
(i.e. self.privatemethod
).
ASubclass.protectedmethod
doesn't work, not because it is "outside the class", but because the sender (in this case the un-named top-level object typically called main
, which is a direct instance of Object
) is not an instance of the module the method is defined in (in this case Aclass.singleton_class
).
A more interesting example
class Superclass
class << self
protected def protected_method; end
end
end
class Subclass < Superclass
Superclass.protected_method
end
Is this allowed or not? (Spoiler alert: the answer will first appear to disappoint you, but then satisfy you nonetheless.)
Well, let's check. Remember the rule for protected
: the sender must be an instance of the module the invoked method is defined in.
So, the module the invoked method is defined in, is Superclass.singleton_class
, i.e. the singleton class of the object Superclass
.
The sender is the object Subclass
.
So, the question is: is the object Subclass
an instance of the class Superclass.singleton_class
? The answer to this is non-obvious.
Clearly, Subclass
is a subclass of Superclass
. But that doesn't help us.
Also, clearly, both Subclass
and Superclass
are instances of Class
(Subclass.class == Class
and Superclass.class == Class
), which also means they are instances of all of Class
's superclasses Module
, Object
, Kernel
, and BasicObject
. But that doesn't help us either: the sender must be an instance of the module the invoked method is defined in. It doesn't matter how many other modules the sender and the receiver have in common in their ancestry, they need to specifically have this module in common (in this case Superclass.singleton_class
)
Also, by the definition of what the singleton class is, Superclass
is an instance of Superclass.singleton_class
and Subclass
is an instance of Subclass.singleton_class
. But that doesn't help us either, since what we would need is for Subclass
to be an instance of Superclass.singleton_class
.
So, it looks like the answer is: No, this is not allowed.
Except, when you try it out, then it works! But, why?
Maybe you have already spotted the "escape hatch" when I quoted section 13.3.5.4 Protected methods of the ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification:
> # 13.3.5.4 Protected methods
> A protected method is a method whose visibility attribute is set to the protected visibility.
>
> A protected method can be invoked if and only if the following condition holds:
>
> * Let M be an instance of the class Module
in which the binding of the method exists.
> M is included in the current self, or M is the class of the current self or one of its superclasses.
>
> If M is a singleton class, whether the method can be invoked or not may be determined in an implementation-defined way.
In particular, pay attention to the last paragraph [bold italic emphasis mine]:
> If M is a singleton class, whether the method can be invoked or not may be determined in an implementation-defined way.
So, the disappointing answer to the question "does this work" is: We don't know. Ruby doesn't say anything about whether or not it works. It depends on the particular implementation.
From the looks of it, it may or may not work, depending on the implementation.
But fear not!
We have the ruby/spec for singleton classes to our rescue! Here's what it says:
> lang-ruby
> describe "A singleton class" do
> it "is a subclass of a superclass's singleton class" do
> ClassSpecs::K.singleton_class.superclass.should ==
> ClassSpecs::H.singleton_class
> end
> end
>
So, it works because the singleton class of the subclass becomes the superclass of the singleton class of the subclass. Let's unpack that.
When I create a subclass of a superclass:
class Superclass; end
class Subclass < Superclass; end
Then the singleton class of the superclass (Superclass.singleton_class
) becomes the superclass of the singleton class (Subclass.singleton_class.superclass
).
Or, in other words, for any class C
, C.singleton_class.superclass == C.superclass.singleton_class
. (Excluding, of course, the top class which has no superclass.)
Since the sender (Subclass
) is an indirect instance (Subclass
is an instance of its own singleton class Subclass.singleton_class
, which in turn is a subclass of Superclass.singleton_class
, therefore Subclass
is indirectly an instance of Superclass.singleton_class
) of the module the invoked method is defined in (Superclass.singleton_class
), invoking a protected
method is allowed, as per the rules for protected
accessibility.
Phew.
And since the ruby/spec applies to all implementations of Ruby (unless explicitly stated otherwise using a guard clause), it is guaranteed that this will be the case in all currently maintained versions of all Ruby implementations.
答案2
得分: 2
这些是封装控制。通常,您会希望坚持使用protected
以防止"外部"1代码干扰内部,但更重要的是为了更好地控制您的公共接口,以及契约。
任何protected
都不应在该类的"家族"之外使用,因此子类可以访问。这是大多数代码使用的方式,因为它通常不关心其他子类。
private
方法的方法更具体,将访问权限限制为派生类。
为什么要这样做?这是情境性的,但常见的原因是您正在编写一个库,而您鼓励的模式是对库中的某些类进行子类化。为了保留封装性,其中一些方法是private
的。
这意味着您可以在将来更改这些方法而不会为依赖于库的任何代码引入破坏性更改。您将它们标记为private
,这意味着"请勿使用,可能会在通知的情况下更改"。
--
1在Ruby中,即使是protected
或private
的代码,您也可以找到一种访问方式,但这被认为是不礼貌和/或有风险的。在其他语言如C++或Java中,编译器通常会拒绝任何此类访问。在Ruby中,您只需行使权力。
英文:
These are encapsulation controls. In general you'll want to stick with protected
to keep "outside"<sup>1</sup> code from messing around with internals, but more importantly, to better control your public interface, and by extension, contract.
Anything protected
is not intended for use outside of the "family" of that class, so subclasses get access. This is what most code uses as it's generally unconcerned about other subclasses.
The private
approach is much more specific and restricts access to derived classes as well.
Why would you want to do this? It's situational, but a common reason is you're writing a library and the pattern you're encouraging is to subclass some classes in the library. In order to preserve encapsulation some of those methods are private
.
This means you can those methods in the future without introducing breaking changes for any code dependent on the library. You labeled them private
, which means "do not use, subject to change without notice".
--
<sup>1</sup> In Ruby you can always find a way to access code even if it's protected
or private
, but it is considered rude and/or risky. In other languages like C++ or Java the compiler will generally flat-out refuse any such access. In Ruby you just pull rank.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论