如何创建私有类方法?


216

这种创建私有类方法的方法如何起作用:

class Person

  def self.get_name
    persons_name
  end

  class << self

    private

    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)"

但这不是:

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

7
我刚看到这篇文章讨论如何创建私有类的方法,并认为这是好的:jakeyesbeck.com/2016/01/24/ruby-private-class-methods/...
内森龙

Answers:


265

private如果要在显式对象上定义方法(在您的情况下self),则似乎不起作用。您可以private_class_method用来将类方法定义为私有方法(或您所描述的方法)。

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

或者(在ruby 2.1+中),由于方法定义返回方法名称的符号,因此您也可以按以下方式使用它:

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

105

ExiRe写道:

红宝石的这种行为确实令人沮丧。我的意思是,如果您转到私有部分self.method,则它不是私有的。但是,如果将其移至<< << self类,那么它将突然起作用。真恶心。

可能令人困惑,令人沮丧,但绝对不是令人恶心。

这非常有意义,一旦你了解Ruby的对象模型和相应的方法查找流动,特别是考虑到时private不是访问/可见性修饰符,但实际上一个方法调用(与类作为其接收方)为讨论在这里 ... Ruby中没有“私有部分”之类的东西。

要定义私有实例方法,您可以调用private实例的类以将随后定义的方法的默认可见性设置为private ...,因此,通过调用类的类(即)来定义私有方法是很有意义private的。它的元类。

其他主流的自称为OO语言的语法可能不太混乱,但是您肯定会在没有Ruby的元编程功能的情况下,将其与混乱且不太一致(不一致)的对象模型进行权衡。


因此,如果我正确理解,ruby本身没有访问修饰符关键字(public,private和protected),而是具有访问修饰符方法(public,private,protected)?这是应该在Matz的ruby bug跟踪器上提出的,以实现适当的关键字访问修饰符吗?还是这是预期的行为?
爱德华

13
@Edward 就是这样设计的junichiito.blogspot.co.uk/2012/03/…。为什么“合适”?
iain

1
接下来,private_class_method :method_name您可以做而不是做private_class_method def method_name...
bjt38 2014年

send(private_method)在对象外部也可以访问。
stevenspiel 2015年

1
@ bjt38为清楚起见,它将是private_class_method def self.method_name
Tom

77

默认情况下,所有类方法都是公共的。要使它们私有,您可以使用Module#private_class_method,就像@tjwallace编写的或不同地定义它们,就像您所做的那样:

class << self

  private

  def method_name
    ...
  end
end

class << self打开self的singleton类,以便可以为当前self对象重新定义方法。这用于定义类/模块(“静态”)方法。只有在那里,定义私有方法才真正为您提供私有类方法。


17

仅出于完整性考虑,我们还可以避免在单独的行中声明private_class_method。我个人不喜欢这种用法,但很高兴知道它的存在。

private_class_method  def self.method_name
 ....
end

5

我也是,在这方面,Ruby(或至少我的知识)不足。例如以下内容是我想要的,但很笨拙,

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

我上面的代码的问题是Ruby语法要求和我的代码质量指标共同构成了繁琐的代码。为了使代码都能按我希望的方式工作并且使指标保持安静,我必须使compare()为类方法。由于我不希望它成为该类的公共API的一部分,因此我需要将其设为私有,但“私有”本身不起作用。相反,我被迫使用'private_class_method'或类似的解决方法。反过来,这迫使我在'==()'中测试的每个变量都使用'self.class.send(:compare ...'),这有点笨拙。


您需要使用send的事实与将类方法标记为私有的“方式”无关。不能从“外部”调用私有方法。
Pascal

4

实例方法在类定义块内定义。类方法被定义为类的单例类上的单例方法,也被非正式地称为“元类”或“本征类”。private不是关键字,而是方法(Module#private)。

这是对方法self#private/ 的调用,A#private它对所有即将到来的实例方法定义“切换”私有访问,直到切换为其他方式:

class A
  private
    def instance_method_1; end
    def instance_method_2; end
    # .. and so forth
end

如前所述,类方法实际上是在单例类上定义的单例方法。

def A.class_method; end

或使用特殊语法打开A的匿名单例类的定义主体:

class << A
  def class_method; end
end

-在“消息私有”的接收器 -内class A是内部的类对象A.自class << A块是另一个对象,该单独的类。

下面的示例实际上是调用两个不同的方法(称为private),使用两个不同的接收者或目标进行调用。在第一部分中,我们定义了一个私有实例方法(“在类A上”),在后一部分中,我们定义了一个私有类方法(实际上是在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

现在,稍微重写一下这个示例:

class A
  private
    def self.class_method; end
end

您能看到[Ruby语言设计师]犯的错误吗?您为所有即将出现的A实例方法启用了私有访问,但是继续在其他类(单例类)上声明单例方法。


-1

Ruby似乎提供了一个糟糕的解决方案。为了说明,从一个简单的C ++示例开始,该示例显示对私有类方法的访问:

#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;
}

运行上面

   % ./a.out
   instance method
   class method

现在,Ruby似乎没有提供相应的功能。我认为,Ruby的规则是不得使用接收方访问私有方法。那是,

inst.pvt_method  # FAILS
pvt_method # WORKS only within the class (good)

私有实例方法可以,但是会导致私有类方法出现问题。

我希望Ruby这样运行:

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

但是,a,上述方法不起作用。有人知道更好的方法吗?

当我在方法之前看到“发送”时,这是一个明显的信号,表明代码违反了API设计人员的意图,但是在这种情况下,设计专门是将类的实例方法称为私有类方法。


-14

从Ruby 2.3.0开始

class Check
  def self.first_method
    second_method
  end

  private
  def self.second_method
    puts "well I executed"
  end
end

Check.first_method
#=> well I executed

private def self.second_method在每种方法表示法之前都尝试过此方法,但在我的红宝石2.3.3上却不起作用。但是这个符号对我有用。
Emile Vrijdags

11
这是不正确的,因为调用Check.second_method也可以毫无问题地进行,因此它不是真正的私有。
Deiwin '17年

1
尝试一下是private_class_method :second_method
行不通的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.