【问题标题】:Use or misuse of private class methods in Ruby在 Ruby 中使用或误用私有类方法
【发布时间】:2016-08-09 08:21:59
【问题描述】:

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

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))的方式。有更好的方法吗?或者,当需要由实例方法调用时,将类方法设为私有通常不是一个好主意?

【问题讨论】:

  • AFAIK,除了#send,别无他法。
  • 私有方法应该被内部方法调用。我们不应该直接从实例中调用它们,所以Example.new.f 调用g 是正确的方法。

标签: ruby


【解决方案1】:

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

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

这意味着private“类方法”只能被其他“类方法”调用,即Example的单例类中定义的方法,ClassModuleObject,@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 是一个合适的对象,甚至可以传递到不同的范围内。

但是,大概您不希望 fg 完全相同(否则您本来可以使用 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 的块)可以访问外部词法范围(在这种情况下是类主体)的词法环境,甚至 该词法范围不再存在(类主体已完成评估)之后。

【讨论】:

  • 这对问题的分析绝对很棒!!!我什至不知道,类定义中的局部变量可能有任何用处!
【解决方案2】:

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

因为一个类和它的实例是完全不同的对象,你必须使用send 或其他元编程技巧。

【讨论】:

    猜你喜欢
    • 2010-09-30
    • 2017-09-17
    • 2015-06-26
    • 1970-01-01
    • 1970-01-01
    • 2013-11-10
    • 1970-01-01
    • 2015-12-08
    • 2010-09-06
    相关资源
    最近更新 更多