【问题标题】:Ruby doesn't have nested methods, right? So what is this, and is there a better way?Ruby 没有嵌套方法,对吧?那么这是什么,还有更好的方法吗?
【发布时间】:2017-01-11 19:37:16
【问题描述】:

我有时会使用这种结构:

class Test
  def today
    date = Date.today

    def date.is_friday?
      strftime("%A") == "Friday"
    end

    date
  end
end

效果很好……

t = Test.new
t.today # => <Date: 2016-09-03 ((2457635j,0s,0n),+0s,2299161j)>
t.today.is_friday? # => false

我一直认为这是一种嵌套方法。但其他人坚持认为 Ruby 没有嵌套方法。好吧,我不迷上这个名字。但你怎么称呼它?有没有更好的方法来实现相同的功能?

【问题讨论】:

  • 为什么不直接使用Date#friday?:t = Test.new.today; t.friday??为什么要在Test.new.today 返回的每个Date 实例上定义一个方法is_friday?
  • 好主意,Cary...对于我展示的示例来说,这将是一个非常好的方法。我可能应该展示一个更接近我通常使用此构造的方式的示例。在 Rails 上下文中,我主要使用#to_s 或#to_json 扩展一些方法。所以可能没有现成的类(例如上面的 Date)来扩展我需要的功能。
  • 我刚好看到你的评论是写给我的。要让我收到此类 cmets 的通知,您需要包含我的用户名或其中的一部分,例如 @cary。

标签: ruby


【解决方案1】:

当人们谈论嵌套的方法/函数时,他们通常指的是仅在封闭方法中可见/可用的东西。您的示例并非如此-一旦定义,任何人都可以在该对象上调用它。

您展示的是 Ruby 的单例方法:在类的一个实例上定义方法的能力(例如,当人们说 ruby​​ 中的类方法时,这些实际上是 Class 类的实例上的单例方法)

至于是否有更好的方法,这是无法回答的,因为您还没有说出您要解决的问题是什么。

【讨论】:

  • 感谢弗雷德里克的洞察力。我试图解决的问题通常是在 Rails 上下文中,其中模型有一个方法,其返回值可能用于其他方法,但我想附加一个 #to_s 扩展以包含在视图中,或者可能附加一个 #to_json 扩展以包含在 api 中。
  • 听起来您可能对装饰器或演示者或自定义序列化器感兴趣
【解决方案2】:

在方法中调用def 通常是错误的方法。您想要的是定义一个模块来封装此方法和可能的其他方法,然后根据需要将其混入任何对象:

module DateExtensions
  def is_friday?
    wday == 5
  end
end

值得注意的是,strftime 可能返回 "Friday" 以外的值,因为本地化可能已生效。根据您所在的位置,您可以获得“Freitag”或“Vendredi”。 wday 方法返回一个可预测的数值。

现在您可以将其混入:

class Test
  def today
    date = Date.today

    date.extend(DateExtensions)

    date
  end
end

通过在模块中声明这些方法,更明显它们是包的一部分。这也意味着您不需要为您创建的每个 Date 实例定义一个全新的方法。

一种更加 Ruby 的方式是定义您自己的 Date 子类,以添加此附加行为。

【讨论】:

  • 这也只定义了该对象的单例类上的方法。我不明白为什么这会更好。现在需要找到“DateExtensions”模块来查看它的实际作用。
  • 这就是为什么我说更 Ruby 的方式是子类。至少使用模块方法,您可以为所有这些被黑客入侵的方法提供一个容器,并且您可以通过这种方式对其进行追踪:date.method(:is_friday?).source_location 我希望从名称中可以明显看出它与原始代码相比“扩展了日期”它根本没有给出上下文。
  • @tadman 我认为子类或模块是 ideomatic ruby​​ 是对的。不过,正如 pascal 所暗示的那样,与我在使用它的地方定义单例方法的技巧相比,当扩展只是一个非常小的方法时,它似乎使代码的可读性降低。我希望有一种方法可以在 Test 类中定义扩展。这可能吗?
  • 如果您使用的是现代版本的 Ruby,您可以随时try using refine to do this
【解决方案3】:

方法定义就像任何其他代码一样是代码。它们可以像任何其他代码一样出现在方法中。就像任何其他代码一样,它们将在每次方法运行时执行。

所以,这个

class Foo
  def foo
    def bar
    end
  end
end

不是嵌套方法,而只是定义方法的方法。很容易看出这不是嵌套方法,只需调用它两次,注意产生的警告并检查周围模块的定义方法:

foo = Foo.new

Foo.public_instance_methods(false)
#=> [:foo]

foo.foo

Foo.public_instance_methods(false)
#=> [:foo, :bar]

foo.foo
# (irb):3: warning: method redefined; discarding old bar
# (irb):3: warning: previous definition of bar was here

如您所见,bar没有嵌套在 foo 中,而是在Foo 类中定义此外foo。它仅在foo 运行后定义,并且每次foo 运行时都会重新定义。

当然,您的示例有点不同,因为它不会一直覆盖同一个类中的方法,而是每次调用时都会在不同的类中定义它。

请注意,有禁止此类使用的计划,请参阅Feature #11665: Support nested functions for better code organization

但是你怎么称呼它?

这是一个定义方法的方法。

还有没有更好的方法来实现相同的功能?

很难说你到底想要达到什么目标。

您可以使用您的方法创建module,并使用它创建extend 所有对象。那将是最接近的类比。

其他解决方案需要对您的代码进行更大规模的重组,例如使用 Decorator Design Pattern 或者,在您的特定 Rails 用例中,使用 Presenter。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 2014-11-21
    • 1970-01-01
    相关资源
    最近更新 更多