在Ruby中使用或滥用私有类方法

时间:2016-08-09 08:22:00

标签: ruby

我有一个类,我希望一个类方法只能被其他类和实例方法使用,但不能使用其他类 - 换句话说,我希望有一个私有类方法。经过一番研究,我提出了以下方法:

class Example
  def initialize
  end  
  def f
    # My private class method will be called here
    self.class.send(:g)
  end
  # g is going to be a private class method
  def self.g
    puts 4711
  end
  private_class_method :g
end

到目前为止这是有效的;调用Example.new.f调用g并调用Example.g会根据需要抛出异常。

什么是尴尬,是调用g(self.class.send(:g))的方法。有没有更好的方法呢?或者,当需要通过实例方法调用类方法时,将类方法设为私有是一个坏主意吗?

2 个答案:

答案 0 :(得分:3)

Ruby中没有“类方法”这样的东西。只有实例方法。 “类方法”实际上是单例方法(在一个恰好恰好是Class的实例的对象上),而“单例方法”只是单例类的实例方法。 / p>

private方法只能使用隐式接收器(self)调用,即它们只能由同一对象上的其他方法调用(这反过来意味着它们必须是同一个类或其祖先之一,即超类,prepend ed模块或include d模块)。

这意味着private“类方法”只能由其他“类方法”调用,即Example的单例类Class中定义的方法,{{1} },ModuleObjectKernel。您无法使用BasicObject中定义的方法调用它们。

想一想:Example的目的是什么?其目的是封装,我们希望从外部协议封装内部实现和表示细节。目前广泛使用的封装有两种:抽象数据类型导向封装(其中不同类型的实例彼此封装,但相同类型的实例可以访问彼此的内部,例如Java中的private es )和面向对象的封装(其中不同的对象彼此封装,甚至,如果它们是相同类型的实例,例如Java中的Ruby和class)。 Ruby是一种面向对象的语言,因此它使用面向对象的封装。 (Java OTOH对类使用面向ADT的封装,这是违反直觉的,因为它通常被声称是面向对象的语言)。

在您的情况下,我们有两个对象interfaceExample的实例。它们是两个不同的对象,因此面向对象的封装只是禁止您从另一个对象访问一个对象的私有部分。即使Ruby确实使用了面向ADT的封装,它仍然无法工作:面向ADT的封装允许两个相同类型的实例访问彼此的私有,但这两个对象的类型不同,一个是实例Example的另一个是Class的实例。

基本上,你想同时操纵两个对象的内部,这在OOP中是不可能的。 OOP的一个基本设计原则是每个对象(以及每个对象)负责其私有部分,并且您只能通过其公共协议发送消息来与该对象进行交互。

tl; dr :您想要的是直接违反OO中的基本封装原则。 Example必须为Example::g,或者您需要更改设计。 (在你无法控制的代码中使用反射性黑客来规避访问保护是最好的代码味道。使用反射性黑客来绕过你拥有的代码中的访问保护是完全错误的,因为你控制访问保护,你可以改变它。)

一种可能的解决方案是完全抛弃OOP,并寻求功能性编程的帮助。我们可以尝试使用闭包进行封装:

我们从您的原始示例开始:

public

现在,我们将class Example private_class_method def self.g puts 4711 end def f self.class.send(:g) end end Example.new.f # 4711 转换为局部变量并为其分配一个lambda,然后使用该lambda来定义g

f

现在,class Example g = -> { puts 4711 } define_method(:f, &g) end Example.new.f # 4711 (在某种意义上)甚至比以前更加“私有”,因为它只存在于类体的词法范围内,甚至不存在于一个不同的班级团体可以访问它。但是,g引用的lambda是一个合适的对象,甚至可以传递到不同的范围。

但是,大概你不希望gf完全相同(毕竟你可能只是使用过g),而是想要{{1}除了委托给module_function之外的其他事情。这也是可能的:

f

这是有效的,因为在某些其他意义上g “私有”比以前:词法范围可以嵌套,特别是块的词法范围(和块)嵌套,以便嵌套块(在这种情况下传递给class Example g = -> { puts 4711 } define_method(:f) do puts 42 g.() end end Example.new.f # 42 # 4711 的块)可以访问外部词法范围的词法环境(在这种情况下是类主体) )甚至之后,词汇范围不再存在(类体已完成评估)。

答案 1 :(得分:1)

您不能直接从其实例方法调用私有类方法,因为Ruby的private实例级私有,这意味着私有方法只能在当前对象上调用(隐式self)。要调用其他对象的私有方法,最简单的方法是使用send

因为类及其实例是完全不同的对象,所以必须使用send或其他元编程技巧。