13

Steve Klabnik 最近在一个实用模块的拉取请求中说:

[代码] 掩盖了这些是类方法的事实,我们希望以这种方式使用它们。另外,我认为这extend self通常是一种反模式,除非在某些情况下,否则不应真正使用。我想了想,我认为这是其中一种情况。

  • 创建实用程序模块(例如,用于数学)时,声明方法的最佳方式是什么?
  • 这个extend self成语什么时候合适?
4

2 回答 2

24

他没有说 1)为什么他认为这是一种反模式(除了掩盖您正在定义类方法的事实之外)或 2)为什么考虑到这是其中一种情况,这并不是很有帮助他认为不应该使用它,因此很难专门反驳他的任何论点。

但是,我不相信这extend self 一种反模式。实用程序模块似乎是它用例的一个很好的例子。我还用它作为测试夹具的简单存储。

我认为值得研究一下它extend self是什么,可能存在哪些问题,以及有哪些替代方案。

从本质上讲,它只是一种避免必须self.在模块中的每个方法定义之前编写的方法,您从不打算将其混合到类中,因此永远不会创建自身的“实例”,因此只能根据定义有“类”方法(如果你想能够调用它们,那就是)。

它是否掩盖了您打算将这些方法用作类方法的事实?好吧,是的,如果您不查看文件顶部的位置extend self,那是可能的。但是,我会争辩说,如果您有可能造成这种混淆,那么无论如何您的课程可能太复杂了。

从你的类——从它的名字和它的内容——应该很明显,它是一个实用函数的集合。理想情况下,无论如何它不会超过屏幕高,因此extend self几乎永远不会消失。正如我们将看到的,替代品也面临几乎完全相同的问题。

一种替代方法是class << self像这样使用:

module Utility
  class << self
    def utility_function1
    end
    def utility_function2
    end
  end
end

我不喜欢这个,尤其是因为它引入了额外的缩进层。它也很丑陋(我知道,这完全是主观的)。它也遇到了完全相同的问题,即“掩盖”您正在定义类方法的事实。

您也可以自由地使用这种方法在class << self块之外定义实例方法 - 这可能会导致这样做的诱惑(尽管我希望它不会),所以我认为这extend self在这方面是优越的通过消除这种水域混浊的可能性。

(当然,使用 . 的“长手”风格也是如此def self.utility_function。)

另一种方法可能是使用单例对象。我认为这根本不是一个好主意,因为单例对象是有原因的对象 - 它旨在保持状态并做一些事情,但也是唯一存在的对象。这对于一个实用模块来说根本没有意义,它应该是一系列独立的无状态函数。您不想MathUtils.cos(90)根据 的内部状态返回不同的值MathUtils,对吗?(我知道你当然可以在一个模块中保持状态并做所有这些事情,但对我来说,这更像是一种语义划分,而不是技术划分)。

它还导致了同样的问题,即可以说掩盖了这些方法旨在被称为类方法(有点)的事实。它们被定义为实例方法,您将它们称为实例方法,但首先通过调用类方法获取类的单个实例instance

class MathSingleton
  include Singleton

  def cos x
  end
end

MathSingleton.instance.cos x

extend self为此目的,这将是一个糟糕的替代方案。但是,请注意,唯一表明这些方法将用作单例实例上的方法的是顶部的一行,就像extend self.

那么还有哪些其他可能的缺点呢?我不知道,但如果其他人知道,我很想听听。

我认为这extend self会导致代码更短,从而省略了无关self.的 s 并允许您专注于其方法的名称和含义。

它还有一个很好的属性,例如,如果您正在编写另一个使用大量实用程序功能的类,您可以将它混合在一起,它们将可用,而无需每次都使用模块名称。就像静态导入在其他语言中一样。

于 2013-05-30T22:08:56.590 回答
0

与 mixins 不同的是,实用程序模块是包含一些常见问题的常量和方法的容器。Mixins,比如这个,

module SingingCapability
  def sing; puts "I'm singing!" end
end

Human = Class.new
Fred = Human.new.tap { |o| o.extend SingingCapability }

通常对它们的包含器提出一些要求。也就是说,通常只有某些对象是包含或扩展给定 mixin 的良好候选对象。假设,一个模块可能同时是一个实用模块和一个 mixin。如果模块本身属于它扩展的合格候选者,那么继续扩展它。

总而言之,我确实认为这不是一个很好的做法,但是 Ruby 在这方面对我不利,因为我们甚至有Module#module_function方法来促进这种弊端:

module SingingBox
  def sing; "Tralala!" end
  module_function :sing
end

SingingBox.sing #=> "Tralala!"
于 2013-05-30T22:22:57.907 回答