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

时间:2011-05-10 01:34:26

标签: ruby methods module

我想定义第二天返回的实例方法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)。 如何使用next模块中声明的方法覆盖Date类中的默认DateExtension方法?

2 个答案:

答案 0 :(得分:103)

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

class Date
  prepend DateExtension
end

旧版Ruby的原始答案如下。


include的问题(如the following diagram所示)是该类的方法不能被该类中包含的模块覆盖(解决方案遵循图表): Ruby Method Lookup Flow

解决方案

  1. 仅针对此方法的子类日期:

    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. 使用您自己的方法扩展您需要的实例:

    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. 在包含你的模块时,执行严重的黑客攻击并进入课堂内部并销毁旧的“下一个”以便你的被调用:

    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
    

    这种方法比仅包含一个模块更具侵入性,但是(目前为止显示的技术)的唯一方法是使系统方法返回的新Date实例将自动使用您自己模块中的方法。

  4. 但如果你打算这样做,你可以完全跳过这个模块,然后直接去monkeypatch土地:

    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. 如果您确实需要在自己的环境中使用此功能,请注意banisterfiend的Prepend库可以让您在混合它的类之前在模块中进行查找。

答案 1 :(得分:7)

next的{​​{1}}方法在Date类中定义,类中定义的方法优先于包含模块中定义的方法。所以,当你这样做时:

Date

您正在提取class Date include DateExtension end 版本,但next中定义的next仍然优先。您必须将Date权限置于next

Date

来自Classes and Objects的编程Ruby章节:

  

当一个类包含一个模块时,该模块的实例方法可用作该类的实例方法。这几乎就像模块成为使用它的类的超类一样。毫不奇怪,这是关于它是如何工作的。当您包含一个模块时,Ruby会创建一个引用该模块的匿名代理类,并将该代理作为执行该类的类的直接超类插入。