在模块中定义的另一个方法覆盖原有方法

41

我想定义一个实例方法 Date#next,用于返回下一天的日期。因此,我创建了一个名为DateExtension的模块,代码如下:

module DateExtension
  def next(symb=:day)
    dt = DateTime.now
    {:day   => Date.new(dt.year, dt.month, dt.day + 1),
     :week  => Date.new(dt.year, dt.month, dt.day + 7),
     :month => Date.new(dt.year, dt.month + 1, dt.day),
     :year  => Date.new(dt.year + 1, dt.month, dt.day)}[symb]
  end
end

使用它:

class Date
  include DateExtension
end

调用方法d.next(:week)会导致Ruby抛出错误ArgumentError: wrong number of arguments (1 for 0)。 我如何使用在DateExtension模块中声明的方法覆盖Date类中的默认next方法?


请仅返回翻译后的文本:dup of https://dev59.com/HG855IYBdhLWcg3wNxgM - akostadinov
2个回答

112

在 Ruby 2.0 及更高版本中,您可以使用 Module#prepend

class Date
  prepend DateExtension
end

以下是针对较旧版本的Ruby的原始答案。


include存在的问题(如下图所示)是类中包含的模块无法重写该类的方法(在图表之后提供解决方案): Ruby Method Lookup Flow

解决方案

  1. Subclass Date just for this one method:

    irb(main):001:0> require 'date'; module Foo; def next(a=:hi); a; end; end
    #=> nil
    irb(main):002:0> class MyDate < Date; include Foo; end
    #=> MyDate
    irb(main):003:0> MyDate.today.next(:world)
    #=> :world
    
  2. Extend just the instances you need with your own method:

    irb(main):001:0> require 'date'; module Foo; def next(a=:hi); a; end; end
    #=> nil
    irb(main):002:0> d = Date.today; d.extend(Foo); d.next(:world)
    #=> :world
    
  3. When including your module, perform a gross hack and reach inside the class and destroy the old 'next' so that yours gets called:

    irb(main):001:0> require 'date'
    #=> true
    irb(main):002:0> module Foo
    irb(main):003:1>   def self.included(klass)
    irb(main):004:2>     klass.class_eval do
    irb(main):005:3*       remove_method :next
    irb(main):006:3>     end
    irb(main):007:2>   end
    irb(main):008:1>   def next(a=:hi); a; end
    irb(main):009:1> end
    #=> nil
    irb(main):010:0> class Date; include Foo; end
    #=> Date
    irb(main):011:0> Date.today.next(:world)
    #=> :world
    

    This method is far more invasive than just including a module, but the only way (of the techniques shown so far) to make it so that new Date instances returned by system methods will automatically use methods from your own module.

  4. But if you're going to do that, you might as well skip the module altogether and just go straight to monkeypatch land:

    irb(main):001:0> require 'date'
    #=> true
    irb(main):002:0> class Date
    irb(main):003:1>   alias_method :_real_next, :next
    irb(main):004:1>   def next(a=:hi); a; end
    irb(main):005:1> end
    #=> nil
    irb(main):006:0> Date.today.next(:world)
    #=> :world
    
  5. If you really need this functionality in your own environment, note that the Prepend library by banisterfiend can give you the ability to cause lookup to occur in a module before the class into which it is mixed.


RUBY_DESCRIPTION =>“ruby 1.8.7(2011-02-18 patchlevel 334)[i386-mingw32]”我刚意识到一件事情。我的代码使用了DateTime类,所以我需要require 'date'(根据这个)。在执行完这句指令后,使用你的示例代码,Ruby会抛出一个错误。因为无法在此处发布整个代码,所以我已经在gist上发布了代码。谢谢! - resilva87
@kaos12 谢谢,我已经编辑了我的答案,并列出了你可以使用的各种选项。 - Phrogz
1
@kaos12 行为没有变化。来自包含的模块的方法总是位于它们所包含到的类的“上方”。请尝试这样做:class MyClass; end; [Object,Class,Module,MyClass].each{ |k| k.class_eval{ define_method(:foo){ puts "#{k}#foo"; super() unless k==Object }}}; MyClass.new.foo 在一个没有包含模块的类上的实例方法直接去访问 Object 上的实例方法。 - Phrogz
1
将来,可能值得研究Module#prepend - Andrew Grimm
1
Module#prepend 在 Ruby 2.0 中已经可用。 - Waiting for Dev...
显示剩余4条评论

7
< p > Date next 方法在 Date 类中定义,而在类中定义的方法优先于包含模块中定义的方法。因此,当您执行以下操作时:

class Date
  include DateExtension
end

您正在使用自己的版本next,但在Date中定义的next仍然具有优先权。您需要将您的next放置在Date中:

class Date
  def next(symb=:day)
    dt = DateTime.now
      {:day   => Date.new(dt.year, dt.month, dt.day + 1),
       :week  => Date.new(dt.year, dt.month, dt.day + 7),
       :month => Date.new(dt.year, dt.month + 1, dt.day),
       :year  => Date.new(dt.year + 1, dt.month, dt.day)}[symb]
    end
end

以下内容来自《Ruby编程》一书中的“类和对象”章节:

当一个类包含一个模块时,该模块的实例方法将成为该类的实例方法。这几乎就像该模块成为使用它的类的超类一样。不出所料,这就是它的工作方式。当你包含一个模块时,Ruby会创建一个匿名代理类,引用该模块,并将该代理插入作为执行包含操作的类的直接超类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接