How to create a private class method?
How come this approach of creating a private class method works:
class Person
def self.get_name
persons_name
end
class << self
private
开发者_StackOverflow中文版 def persons_name
"Sam"
end
end
end
puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name #=> raises "private method `persons_name' called for Person:Class (NoMethodError)"
But this does not:
class Person
def self.get_name
persons_name
end
private
def self.persons_name
"Sam"
end
end
puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
private
doesn't seem to work if you are defining a method on an explicit object (in your case self
). You can use private_class_method
to define class methods as private (or like you described).
class Person
def self.get_name
persons_name
end
def self.persons_name
"Sam"
end
private_class_method :persons_name
end
puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
Alternatively (in ruby 2.1+), since a method definition returns a symbol of the method name, you can also use this as follows:
class Person
def self.get_name
persons_name
end
private_class_method def self.persons_name
"Sam"
end
end
puts "Hey, " + Person.get_name
puts "Hey, " + Person.persons_name
ExiRe wrote:
Such behavior of ruby is really frustrating. I mean if you move to private section self.method then it is NOT private. But if you move it to class << self then it suddenly works. It is just disgusting.
Confusing it probably is, frustrating it may well be, but disgusting it is definitely not.
It makes perfect sense once you understand Ruby's object model and the corresponding method lookup flow, especially when taking into consideration that private
is NOT an access/visibility modifier, but actually a method call (with the class as its recipient) as discussed here... there's no such thing as "a private section" in Ruby.
To define private instance methods, you call private
on the instance's class to set the default visibility for subsequently defined methods to private... and hence it makes perfect sense to define private class methods by calling private
on the class's class, ie. its metaclass.
Other mainstream, self-proclaimed OO languages may give you a less confusing syntax, but you definitely trade that off against a confusing and less consistent (inconsistent?) object model without the power of Ruby's metaprogramming facilities.
By default all class methods are public. To make them private you can use Module#private_class_method like @tjwallace wrote or define them differently, as you did:
class << self
private
def method_name
...
end
end
class << self
opens up self's singleton class, so that methods can be redefined for the current self object. This is used to define class/module ("static") method. Only there, defining private methods really gives you private class methods.
Just for the completeness, we can also avoid declaring private_class_method in a separate line. I personally don't like this usage but good to know that it exists.
private_class_method def self.method_name
....
end
Instance methods are defined inside a class definition block. Class methods are defined as singleton methods on the singleton class of a class, also informally known as the "metaclass" or "eigenclass". private
is not a keyword, but a method (Module#private).
This is a call to method self#private
/A#private
which "toggles" private access on for all forthcoming instance method definitions until toggled otherwise:
class A
private
def instance_method_1; end
def instance_method_2; end
# .. and so forth
end
As noted earlier, class methods are really singleton methods defined on the singleton class.
def A.class_method; end
Or using a special syntax to open the definition body of the anonymous, singleton class of A:
class << A
def class_method; end
end
The receiver of the "message private" - self - inside class A
is the class object A. self inside the class << A
block is another object, the singleton class.
The following example is in reality calling two different methods called private, using two different recipients or targets for the call. In the first part, we define a private instance method ("on class A"), in the latter we define a private class method (is in fact a singleton method on the singleton class object of A).
class A
# self is A and private call "A.private()"
private def instance_method; end
class << self
# self is A's singleton class and private call "A.singleton_class.private()"
private def class_method; end
end
end
Now, rewrite this example a bit:
class A
private
def self.class_method; end
end
Can you see the mistake [that Ruby language designers] made? You toggle on private access for all forthcoming instance methods of A, but proceed to declare a singleton method on a different class, the singleton class.
I too, find Ruby (or at least my knowledge of it) short of the mark in this area. For instance the following does what I want but is clumsy,
class Frob
attr_reader :val1, :val2
Tolerance = 2 * Float::EPSILON
def initialize(val1, val2)
@val2 = val1
@val2 = val2
...
end
# Stuff that's likely to change and I don't want part
# of a public API. Furthermore, the method is operating
# solely upon 'reference' and 'under_test' and will be flagged as having
# low cohesion by quality metrics unless made a class method.
def self.compare(reference, under_test)
# special floating point comparison
(reference - under_test).abs <= Tolerance
end
private_class_method :compare
def ==(arg)
self.class.send(:compare, val1, arg.val1) &&
self.class.send(:compare, val2, arg.val2) &&
...
end
end
My problems with the code above is that the Ruby syntax requirements and my code quality metrics conspire to made for cumbersome code. To have the code both work as I want and to quiet the metrics, I must make compare() a class method. Since I don't want it to be part of the class' public API, I need it to be private, yet 'private' by itself does not work. Instead I am force to use 'private_class_method' or some such work-around. This, in turn, forces the use of 'self.class.send(:compare...' for each variable I test in '==()'. Now that's a bit unwieldy.
Ruby seems to provide a poor solution. To explain, start with a simple C++ example that shows access to private class methods:
#include <iostream>
class C
{
public:
void instance_method(void)
{
std::cout << "instance method\n";
class_method(); // !!! LOOK !!! no 'send' required. We can access it
// because 'private' allows access within the class
}
private:
void static class_method(void) { std::cout << "class method\n"; }
};
int main()
{
C c;
c.instance_method(); // works
// C::class_method() does not compile - it's properly private
return 0;
}
Running the above
% ./a.out
instance method
class method
Now Ruby does not seem to provide the equivalent. Ruby's rules, I think, are that private methods must not be accessed with a receiver. That is,
inst.pvt_method # FAILS
pvt_method # WORKS only within the class (good)
That's OK for private instance methods, but causes problems with private class methods.
I would like Ruby to function this way:
class C
def instance_method
STDOUT << "instance method\n"
# Simple access to the private class method would be nice:
class_method # DOES NOT WORK. RUBY WON'T FIND THE METHOD
C.class_method # DOES NOT WORK. RUBY WON'T ALLOW IT
# ONLY THIS WORKS. While I am happy such capability exists I think
# the way 'send' should be used is when the coder knows he/she is
# doing a no-no. The semantic load on the coder for this is also
# remarkably clumsy for an elegant language like ruby.
self.class.send(:class_method)
end
private_class_method def self.class_method() STDOUT << "class method\n"; end
end
But, alas, the above does not work. Does someone know a better way?
When I see 'send' prior to a method, it's a clear sign the code is violating the intent of the API's designer, but in this case the design is specifically to have an instance method of the class call the private class method.
精彩评论