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

res*_*a87 37 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
Run Code Online (Sandbox Code Playgroud)

使用它:

class Date
  include DateExtension
end
Run Code Online (Sandbox Code Playgroud)

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

Phr*_*ogz 107

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

class Date
  prepend DateExtension
end
Run Code Online (Sandbox Code Playgroud)

旧版Ruby版本的原始答案如下.


的问题include(如在所示的下图)是一类的方法不能被包括在类(溶液遵循图)模块来覆盖: Ruby方法查找流程

解决方案

  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
    
    Run Code Online (Sandbox Code Playgroud)
  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
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在包含你的模块时,执行一个严重的黑客攻击并进入类内部并销毁旧的'next'以便调用你的模块:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    这种方法比仅包含一个模块更具侵入性,但是(目前为止所示技术)的唯一方法是使系统方法返回的新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
    
    Run Code Online (Sandbox Code Playgroud)
  5. 如果您确实需要在自己的环境中使用此功能,请注意banisterfiend的Prepend库可以使您能够在混合它的类之前在模块中进行查找.


mu *_*ort 7

类中定义的next方法和类中Date定义的Date方法优先于包含模块中定义的方法.所以,当你这样做时:

class Date
  include DateExtension
end
Run Code Online (Sandbox Code Playgroud)

您正在提取您的版本,nextnext定义的Date仍然优先.你必须把你的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
Run Code Online (Sandbox Code Playgroud)

从关于类和对象的编程Ruby章节:

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