Как переопределить метод статического класса, используя модуль в Ruby? - PullRequest
10 голосов
/ 03 февраля 2012
module Imodule
  ???
end

class Some
  include Imodule

  def self.imethod
    puts "original"
  end
end

Some.imethod
# => "overrided"

Как создать модуль, который будет переопределять статический метод?

Это вопрос интервью для глубокого понимания функций ruby.Не предлагайте другую формулировку проблемы:)

Ответы [ 3 ]

31 голосов
/ 03 февраля 2012

Хорошо, вот рабочий код.Обратите внимание, что вам даже не нужно трогать целевой класс!:)

class Klass
  def self.say
    puts 'class'
  end
end

module FooModule
  def self.included base
    base.instance_eval do
      def say
        puts "module"
      end
    end
  end
end


Klass.send(:include, FooModule)

Klass.say

Объяснение

Теперь классический способ смешивания в методах класса таков (и, конечно, это не решает проблему).

module FooModule
  def self.included base
    base.extend ClassMethods
  end

  module ClassMethods
    def bar
      puts "module"
    end
  end
end

class Klass
  include FooModule

  def self.bar
    puts 'class'
  end
end


Klass.bar #=> class

Когда модули включены или расширены в класс, его методы размещаются прямо над методами этого класса в цепочке наследования.Это означает, что если бы мы вызвали super в этом методе класса, он напечатал бы «модуль».Но мы не хотим касаться оригинального определения класса, мы хотим изменить его извне.

Итак, можем ли мы что-то сделать?

Хорошо для нас, у ruby ​​есть концепция «открытые уроки» .Это означает, что мы можем изменить практически все в приложении, даже некоторые сторонние библиотеки.Каждый класс может быть «открыт» и новые методы могут быть добавлены к нему, или старые методы могут быть переопределены.Давайте посмотрим, как это работает.

class Klass
  def self.bar
    puts 'class'
  end
end

class Klass
  def self.bar
    puts 'class 2'
  end
end

Klass.bar #=> class 2

Определение второго класса не переписывает предыдущее, оно открывает и изменяет его.В этом случае случилось, чтобы определить метод с тем же именем.Это привело к тому, что старый метод был заменен новым.Это работает с любыми классами, даже с базовыми библиотеками.

puts [1, 2, 3].to_s #=> [1, 2, 3]

class Array
  def to_s
    "an array: #{join ', '}"
  end
end

puts [1, 2, 3].to_s #=> an array: 1, 2, 3

Или тот же код можно переписать как

puts [1, 2, 3].to_s #=> [1, 2, 3]

Array.class_eval do
  def to_s
    "an array: #{join ', '}"
  end
end

puts [1, 2, 3].to_s #=> an array: 1, 2, 3

Применение знаний

Давайте начнем с более простыхтакие вещи, как переопределение метода экземпляра.

class Klass
  def say
    puts 'class'
  end
end

module FooModule
  def self.included base
    base.class_eval do
      def say
        puts "module"
      end
    end
  end
end


Klass.send(:include, FooModule)

Klass.new.say #=> module

Модули имеют специальный обратный вызов, который вызывается каждый раз, когда модуль включается в класс.Мы можем использовать это для вызова class_eval для этого класса и переопределения метода.

Замена метода класса выполняется аналогичным образом.

class Klass
  def self.say
    puts 'class'
  end
end

module FooModule
  def self.included base
    base.instance_eval do
      def say
        puts "module"
      end
    end
  end
end


Klass.send(:include, FooModule)

Klass.say #=> module

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

Это взято из моего сообщения в блоге .

4 голосов
/ 06 января 2015

Что если вам нужно будет вызвать оригинальный метод, который вы только что переопределили, из вашего нового метода?

Другими словами, что если вы хотите иметь возможность вызывать super из переопределенного метода класса так же, как вы можете вызвать super при переопределении метода экземпляра

Вот решение, к которому я наконец пришел, на случай, если кто-нибудь еще найдет его полезным:

class Klass
  def self.say
    puts 'original, '
  end
end

module FooModule
  def self.included base
    orig_method = base.method(:say)
    base.define_singleton_method :say do
      orig_method.call
      puts "module"
    end
  end
end


class Klass
  include FooModule
end

Klass.say  # => original, module

Мы должны использовать define_method вместо def, чтобы мы создали замыкание и получили доступ к локальным переменным (в данном случае, к сохраненной версии исходного метода) из определения нового метода.

Кстати,

base.define_singleton_method :say do

эквивалентно

(class << base; self; end).send :define_method, :say do

.

Отдельное спасибо Синглтон-методам Ruby с (class_eval, define_method) против (instance_eval, define_method) и http://yugui.jp/articles/846 за то, что обучили меня и указали в правильном направлении.

1 голос
/ 03 февраля 2012

Если это на самом деле - вопрос интервью, чтобы проверить глубокое понимание возможностей Ruby, то ответ тривиален: у Ruby нет методов класса. Также у него нет статических методов. У этого наверняка нет статических методов класса. Если у вас есть глубокое понимание возможностей Ruby, черт возьми, даже если вы просто поверхностно знакомы с Ruby, вы это знаете. Ergo, это вопрос с подвохом.

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