Steve Klabnik 最近在一个实用模块的拉取请求中说:
[代码] 掩盖了这些是类方法的事实,我们希望以这种方式使用它们。另外,我认为这
extend self
通常是一种反模式,除非在某些情况下,否则不应真正使用。我想了想,我认为这是其中一种情况。
- 创建实用程序模块(例如,用于数学)时,声明方法的最佳方式是什么?
- 这个
extend self
成语什么时候合适?
他没有说 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 并允许您专注于其方法的名称和含义。
它还有一个很好的属性,例如,如果您正在编写另一个使用大量实用程序功能的类,您可以将它混合在一起,它们将可用,而无需每次都使用模块名称。就像静态导入在其他语言中一样。
与 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!"