“扩展自我”是否是实用模块的反模式?

时间:2013-05-30 21:28:22

标签: ruby

史蒂夫·克拉布尼克最近在pull request中说了一个实用程序模块:

  

[代码]模糊了这些是类方法的事实,而且我们   想以这种方式使用它们。另外,我认为extend self一般   反模式,除非在某些情况下,否则不应该使用。一世   想到这一点,我认为这是其中一例。

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

2 个答案:

答案 0 :(得分:24)

他没有说1)为什么他认为这是一种反模式(除了模糊你正在定义类方法的事实)或2)为什么,考虑到它,这是一个不是很有帮助在他认为不应该使用的案件中,所以很难具体反驳他的任何论点。

但是,我不相信extend self 是反模式。实用程序模块似乎是一个用例的好例子。我也将它用作测试夹具的简易存储空间。

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

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

它是否掩盖了您打算将这些方法用作类方法的事实?好吧,是的,如果你不看文件的顶部到extend self的位置,那就可以了。但是,我认为,如果你有可能让这种混乱,你的课程可能太复杂了。

从你的班级 - 从名称和内容 - 中可以明显看出它是作为效用函数的集合。理想情况下,它不会比屏幕更高,所以extend self几乎永远不会出现。正如我们所看到的,替代方案也会遇到几乎完全相同的问题。

另一种方法是像这样使用class << self

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

我不喜欢这个,不仅仅是因为它引入了额外的缩进层。它也很难看(完全主观,我知道)。它也会遇到完全相同的“模糊”你正在定义类方法这一事实的问题。

使用这种方法你也可以自由地定义class << self块之外的实例方法 - 这可能导致这样做的诱惑(尽管我希望它不会),所以我' d认为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.,并允许您专注于名称及其方法的含义。

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

答案 1 :(得分:0)

与mixin相反,实用程序模块是包含常量和方法的容器,这些容器存在一些共同的问题。 Mixins,比如这个,

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

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

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

总而言之,我确实认为这有点不是一个很好的做法,但Ruby因为我们甚至采用Module#module_function方法来促成这种不正当行为而在这一方面无视我:

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

SingingBox.sing #=> "Tralala!"