【问题标题】:Namespacing module method definition命名空间模块方法定义
【发布时间】:2020-03-30 08:45:34
【问题描述】:

我无意中在 Ruby 的Postgresql gem 中看到了这段代码:

### Convenience alias for PG::Connection.new.
def self::connect( *args )
  return PG::Connection.new( *args )
end

我玩了一下,发现这个东西就像一个普通的模块类方法一样使用(它被称为:PG.connect)。事实上,我们可以改为使用def self.connect ( #args ) ... end,它的工作原理是一样的。

由于self::whatever 对我来说是第一个,我想知道self 命名空间在这种情况下到底是什么,在这种情况下它的真正目的是什么。任何人都可以帮助阐明这一点吗?

【问题讨论】:

  • 相当于def self.connect
  • 当您将代码放入类/模块定义中时,它就像任何其他 Ruby 代码一样运行。唯一真正的区别是自我的价值。正如您在下面看到的, self 指向正在定义的类或模块。 class Ghost self == Ghost # => true end module Mummy self == Mummy # => true end

标签: ruby-on-rails ruby postgresql pg


【解决方案1】:

:: 在某些情况下,在某种程度上等同于.。 (我将省略一个精确的定义,在哪些确切情况下它是等价的以及在什么程度上,因为我坦率地承认我自己并不完全了解它,也没有充分的记录。)

以下是::. 行为不同的示例:

module Foo
  def self.Bar
    'method'
  end

  Bar = 'constant'

  def self.Qux; end
end

Foo.Bar
#=> 'method'

Foo::Bar
#=> 'constant'

Foo::Bar()
#=> 'method'

Foo.Qux

Foo::Qux
# NameError (uninitialized constant Foo::Qux)

Foo::Qux()

[注意:这不是一个完美的示例,因为这是关于事物的消息发送方面,而您的示例是关于事物的方法定义方面。我相信在方法定义方面,它们是 100% 相同的,因为 def 从未定义常量,因此没有歧义。]

所有样式指南都强烈反对这种用法,从强烈建议反对它的消息感知部分到完全禁止它的两种用法。造成这种情况的主要原因是两者在消息发送方面的行为不同,这可能会导致混淆。解释方法查找(动态,在继承链中向上)和常量查找(首先是静态的,词法向外,然后是动态的,在继承链中向上)之间的概念差异也更简单。

很多 Rubyists 使用的典型风格是:

  • 切勿将:: 用于单例方法定义,始终使用.
  • 始终使用:: 在文档中引用单例方法,切勿使用.
  • 在文档中始终使用 . 作为消息发送(包括单例方法)的使用示例,切勿使用 ::
  • 切勿将:: 用于消息发送(包括单例方法),始终使用.

最后两个有时被软化以允许应该返回模块或类的方法,并允许通过“看起来像”常量查找的消息发送调用充当工厂的方法(例如 Nokogiri::XML) ,(例如Nokogiri::XML('<root/>') 而不是Nokogiri.XML('<root/>'))。

有一个 feature request 用于删除 :: 的这种用法,但它是 rejected 主要是因为向后兼容性问题。

【讨论】:

  • 很好的例子,但@max 在他的评论中几乎指出了这一点:“这里的消息是 define_method(:connect) do |*args|; ... endself 是收件人”。因此,如果 def self::some_method(*args) ... end 等于 define_method(:some_method) do |*args|; ... end 则就像向 self 发送消息以在其中定义一个完全有意义的方法。
【解决方案2】:

:: 是范围解析运算符。所以 self::connect 从 self 解析 connect。这意味着它相当于self.connect。你可以从这个非常人为的例子中看到它是如何工作的:

class Foo; end

class Bar
  def Foo::baz
    "Hello World"
  end
end

puts Foo.baz # "Hello World"

当然,我们也可以只使用def Foo.baz 来获得完全相同的结果。

Ruby Style guide 不鼓励在方法定义中使用双冒号:

不要使用 :: 来定义类方法。

# bad
class Foo
  def self::some_method
  end
end

# good
class Foo
  def self.some_method
  end
end

除了引用常量和构造函数之外,也不推荐使用它:

仅使用 :: 来引用常量(这包括类和模块) 和构造函数(如 Array() 或 Nokogiri::HTML())。不要使用 :: 表示 常规方法调用。

【讨论】:

  • So self::connect resolves connect from self. Which means that its equivalent to self.connect 成功了。所以我猜样式指南不鼓励这样做的全部原因是因为我们可以在不同的类/模块中定义属于不同类/模块的方法,这会使代码变得一团糟,对吧?
  • 您也可以在没有范围解析运算符的情况下执行此操作。不鼓励它的原因可能只是可读性。 :: 意味着我们正在解析一个常量,而不仅仅是将消息传递给接收者(这实际上是正在发生的事情)。这里的邮件是define_method(:connect) do |*args|; ... endself是收件人。
  • 定义属于不同类/模块的方法通常不被视为糟糕的 OOP,因为它违反了封装。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多