Переопределение метода другим определенным в модуле - PullRequest
36 голосов
/ 10 мая 2011

Я хочу определить метод экземпляра 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 ]

104 голосов
/ 10 мая 2011

В Ruby 2.0 и более поздних версиях вы можете использовать Module#prepend:

class Date
  prepend DateExtension
end

Оригинальный ответ для более старых версий Ruby приведен ниже.


Проблема с include (как показано в на следующей диаграмме ) заключается в том, что методы класса не могут быть переопределены модулями, включенными в этот класс (решения следуют за схемой): 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 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. Если вам действительно нужна эта функциональность в вашей собственной среде, обратите внимание, что Prepend library by banisterfiend может дать вам возможность вызвать поиск в модуле перед классом, в который он входит.

    • Обратите внимание, что Module#prepend выглядит как в Ruby 2.0 .
7 голосов
/ 10 мая 2011

Метод next для Date определен в классе Date, и методы, определенные в классе, имеют приоритет над методами, определенными во включенном модуле.Итак, когда вы делаете это:

class Date
  include DateExtension
end

Вы загружаете свою версию next, но next, определенный в 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

Из главы Ruby по программированию о Классы и объекты :

Когда класс включает модуль, методы экземпляра этого модуля становятся доступными как методы экземпляра класса.Это почти как если бы модуль стал суперклассом класса, который его использует.Не удивительно, что это о том, как это работает.Когда вы включаете модуль, Ruby создает анонимный прокси-класс, который ссылается на этот модуль, и вставляет этот прокси в качестве прямого суперкласса класса, который сделал включение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...