Ruby 中没有“类方法”这样的东西。只有实例方法。 “类方法”实际上是单例方法(在恰好是Class 实例的对象上),而“单例方法”只是单例类的实例方法。
private 方法只能通过隐式接收器(即self)调用,即它们只能由同一对象上的其他方法调用(这反过来意味着它们必须是同一类的方法或它的祖先之一,即超类、prepended 模块或included 模块)。
这意味着private“类方法”只能被其他“类方法”调用,即Example的单例类中定义的方法,Class,Module,Object,@987654331 @,或BasicObject。您不能从Example 中定义的方法调用它们。
想一想:private 的目的是什么?它的目的是封装,我们想从external协议中封装internal实现和表示细节。目前广泛使用的封装有两种:Abstract Data Type-Oriented Encapsulation(不同类型的实例相互封装,但相同类型的实例可以访问彼此的内部,例如Java中的classes)和面向对象的封装(不同的对象相互封装,甚至如果它们是相同类型的实例,例如 Ruby 和 Java 中的interfaces)。 Ruby 是一种面向对象的语言,因此它使用面向对象的封装。 (Java OTOH 对类使用面向 ADT 的封装,这是违反直觉的,因为它通常被称为面向对象的语言)。
在你的例子中,我们有两个对象,Example 和一个 Example 的实例。它们是两个不同的对象,因此面向对象的封装只是禁止您从另一个对象访问一个对象的私有部分。即使 Ruby 确实使用了面向 ADT 的封装,它仍然行不通:面向 ADT 的封装允许相同类型的两个实例访问彼此的私有,但是这两个对象的类型不同,一个是实例Class 的一个实例,另一个是Example 的一个实例。
基本上,您希望同时操作两个对象的内部结构,而这在 OOP 中是不可能的。 OOP 的基本设计原则是,每个对象(以及每个对象单独)负责其私有部分,并且您只能通过其公共协议向该对象发送消息来与该对象进行交互。
tl;dr:你想要的直接违背了 OO 中的基本封装原则。 Example::g 必须是 public,否则您需要更改设计。 (在您无法控制的代码中使用反射式黑客来规避访问保护充其量是一种代码味道。在您拥有的代码中使用反射式黑客来规避访问保护是完全错误的,因为您可以控制访问保护,你可以改变它。)
一种可能的解决方案是完全放弃 OOP,并寻求函数式编程的帮助。我们可以尝试使用 closures 进行封装:
我们从你原来的例子开始:
class Example
private_class_method def self.g
puts 4711
end
def f
self.class.send(:g)
end
end
Example.new.f
# 4711
现在,我们将g 转换为局部变量并为其分配一个 lambda,然后使用该 lambda 定义f:
class Example
g = -> {
puts 4711
}
define_method(:f, &g)
end
Example.new.f
# 4711
现在,g(在某种意义上)甚至比以前“私有”了,因为它只存在于类主体的词法范围内,甚至不存在于不同的类中定义的类方法类主体可以访问它。但是,g 引用的 lambda 是一个合适的对象,甚至可以传递到不同的范围内。
但是,大概您不希望 f 和 g 完全相同(否则您本来可以使用 module_function,毕竟),而是希望 f 做一些其他事情,而不仅仅是委托到g。这也是可能的:
class Example
g = -> {
puts 4711
}
define_method(:f) do
puts 42
g.()
end
end
Example.new.f
# 42
# 4711
这是可行的,因为在某些其他意义上,g 比以前更少“私有”:词法范围可以嵌套,尤其是块的词法范围(和only 块)嵌套,这样嵌套块(在这种情况下是传递给define_method 的块)可以访问外部词法范围(在这种情况下是类主体)的词法环境,甚至 在该词法范围不再存在(类主体已完成评估)之后。