如何在Ruby中使用模块覆盖静态类方法?

时间:2022-06-27 10:21:00
module Imodule
  ???
end

class Some
  include Imodule

  def self.imethod
    puts "original"
  end
end

Some.imethod
# => "overrided"

How to create a module which will override static method?

如何创建一个覆盖静态方法的模块?

This is an interview question for deep understanding ruby features. Don't suggest another formulation of the problem :)

这是一个深入了解ruby功能的面试问题。不建议另一个问题的表述:)

3 个解决方案

#1


30  

Ok, here's a working code. Note that you don't even have to touch target class! :)

好的,这是一个有效的代码。请注意,您甚至不必触及目标类! :)

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

Explanation

Now classic way of mixing in class methods is this (and it doesn't solve the problem, of course).

现在,在类方法中混合的经典方法就是这样(当然,它并没有解决问题)。

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

When modules are included or extended into a class, its methods are placed right above this class' methods in inheritance chain. This means that if we were to call super in that class method, it would print "module". But we don't want to touch original class definition, we want to alter it from outside.

当模块被包含或扩展到类中时,它的方法就位于继承链中这个类的方法的正上方。这意味着如果我们在该类方法中调用super,它将打印“module”。但是我们不想触及原始类定义,我们希望从外部改变它。

So, can we do something?

Good for us, ruby has a concept of "open classes". This means that we can change virtually everything in the app, even some 3rd-party libraries. Every class can "opened" and new methods can be added to it, or old methods can be redefined. Let's look how it works.

对我们有好处,红宝石有一个“公开课”的概念。这意味着我们几乎可以更改应用程序中的所有内容,甚至是某些第三方库。每个类都可以“打开”,并且可以向其添加新方法,或者可以重新定义旧方法。让我们来看看它是如何工作的。

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

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

Klass.bar #=> class 2

The second class definition does not overwrite previous one, it opens and alters it. In this case, it happened to define a method with the same name. This resulted in old method being overwritten by the new one. This works with any classes, even base library classes.

第二个类定义不会覆盖前一个定义,它会打开并更改它。在这种情况下,碰巧定义了一个具有相同名称的方法。这导致旧方法被新方法覆盖。这适用于任何类,甚至是基本库类。

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

Or the same code can be rewritten as

或者相同的代码可以重写为

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

Applying the knowledge

Let's start with simpler things, like overriding an instance method.

让我们从更简单的事情开始,比如重写实例方法。

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

Modules have a special callback that gets called every time a module is included in a class. We can use that to call class_eval on that class and redefine a method.

模块有一个特殊的回调,每次模块包含在类中时都会调用它。我们可以使用它来调用该类的class_eval并重新定义一个方法。

Replacing a class method is done in a similar way.

以类似的方式替换类方法。

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

The only difference here is that we call instance_eval instead of class_eval. This can be a very confusing part. In short, class_eval creates instance methods and instance_eval creates class methods.

这里唯一的区别是我们调用instance_eval而不是class_eval。这可能是一个非常令人困惑的部分。简而言之,class_eval创建实例方法,instance_eval创建类方法。

This is taken from my blog post.

这是从我的博客文章中获取的。

#2


3  

What if you need to be able to call the original method that you just overrode, from within your new method?

如果您需要能够在新方法中调用刚刚覆盖的原始方法,该怎么办?

In other words, what if you want to be able to call super from an overridden class method the same way you might call super when overriding an instance method?

换句话说,如果您希望能够以重写实例方法时调用super的相同方式从重写类方法调用super,该怎么办?

Here's the solution I finally arrived upon, in case anyone else finds it useful:

这是我最终得到的解决方案,以防其他人发现它有用:

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

We have to use define_method instead of def so that we create a closure and have access to local variables (in this case, the saved version of the original method) from within the new method definition.

我们必须使用define_method而不是def,这样我们就可以在新方法定义中创建一个闭包并访问局部变量(在本例中是原始方法的保存版本)。

By the way,

顺便一提,

base.define_singleton_method :say do

is equivalent to doing

等同于做

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

.

Special thanks to Ruby singleton methods with (class_eval, define_method) vs (instance_eval, define_method) and http://yugui.jp/articles/846 for educating me and pointing me in the right direction.

特别感谢Ruby单例方法(class_eval,define_method)vs(instance_eval,define_method)和http://yugui.jp/articles/846教育我并指出我正确的方向。

#3


1  

If this really is an interview question to test deep understanding of Ruby features, then the answer is trivial: Ruby doesn't have class methods. Nor does it have static methods. It most certainly doesn't have static class methods. If you have a deep understanding of Ruby features, heck, even if you have just a superficial familiarity with Ruby, you know this. Ergo, it's a trick question.

如果这确实是一个面试问题来测试对Ruby特性的深刻理解,那么答案是微不足道的:Ruby没有类方法。它也没有静态方法。它肯定没有静态类方法。如果你对Ruby特性有深刻的理解,那么,即使你只是对Ruby有一种肤浅的熟悉,你也知道这一点。这是一个棘手的问题。

#1


30  

Ok, here's a working code. Note that you don't even have to touch target class! :)

好的,这是一个有效的代码。请注意,您甚至不必触及目标类! :)

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

Explanation

Now classic way of mixing in class methods is this (and it doesn't solve the problem, of course).

现在,在类方法中混合的经典方法就是这样(当然,它并没有解决问题)。

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

When modules are included or extended into a class, its methods are placed right above this class' methods in inheritance chain. This means that if we were to call super in that class method, it would print "module". But we don't want to touch original class definition, we want to alter it from outside.

当模块被包含或扩展到类中时,它的方法就位于继承链中这个类的方法的正上方。这意味着如果我们在该类方法中调用super,它将打印“module”。但是我们不想触及原始类定义,我们希望从外部改变它。

So, can we do something?

Good for us, ruby has a concept of "open classes". This means that we can change virtually everything in the app, even some 3rd-party libraries. Every class can "opened" and new methods can be added to it, or old methods can be redefined. Let's look how it works.

对我们有好处,红宝石有一个“公开课”的概念。这意味着我们几乎可以更改应用程序中的所有内容,甚至是某些第三方库。每个类都可以“打开”,并且可以向其添加新方法,或者可以重新定义旧方法。让我们来看看它是如何工作的。

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

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

Klass.bar #=> class 2

The second class definition does not overwrite previous one, it opens and alters it. In this case, it happened to define a method with the same name. This resulted in old method being overwritten by the new one. This works with any classes, even base library classes.

第二个类定义不会覆盖前一个定义,它会打开并更改它。在这种情况下,碰巧定义了一个具有相同名称的方法。这导致旧方法被新方法覆盖。这适用于任何类,甚至是基本库类。

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

Or the same code can be rewritten as

或者相同的代码可以重写为

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

Applying the knowledge

Let's start with simpler things, like overriding an instance method.

让我们从更简单的事情开始,比如重写实例方法。

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

Modules have a special callback that gets called every time a module is included in a class. We can use that to call class_eval on that class and redefine a method.

模块有一个特殊的回调,每次模块包含在类中时都会调用它。我们可以使用它来调用该类的class_eval并重新定义一个方法。

Replacing a class method is done in a similar way.

以类似的方式替换类方法。

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

The only difference here is that we call instance_eval instead of class_eval. This can be a very confusing part. In short, class_eval creates instance methods and instance_eval creates class methods.

这里唯一的区别是我们调用instance_eval而不是class_eval。这可能是一个非常令人困惑的部分。简而言之,class_eval创建实例方法,instance_eval创建类方法。

This is taken from my blog post.

这是从我的博客文章中获取的。

#2


3  

What if you need to be able to call the original method that you just overrode, from within your new method?

如果您需要能够在新方法中调用刚刚覆盖的原始方法,该怎么办?

In other words, what if you want to be able to call super from an overridden class method the same way you might call super when overriding an instance method?

换句话说,如果您希望能够以重写实例方法时调用super的相同方式从重写类方法调用super,该怎么办?

Here's the solution I finally arrived upon, in case anyone else finds it useful:

这是我最终得到的解决方案,以防其他人发现它有用:

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

We have to use define_method instead of def so that we create a closure and have access to local variables (in this case, the saved version of the original method) from within the new method definition.

我们必须使用define_method而不是def,这样我们就可以在新方法定义中创建一个闭包并访问局部变量(在本例中是原始方法的保存版本)。

By the way,

顺便一提,

base.define_singleton_method :say do

is equivalent to doing

等同于做

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

.

Special thanks to Ruby singleton methods with (class_eval, define_method) vs (instance_eval, define_method) and http://yugui.jp/articles/846 for educating me and pointing me in the right direction.

特别感谢Ruby单例方法(class_eval,define_method)vs(instance_eval,define_method)和http://yugui.jp/articles/846教育我并指出我正确的方向。

#3


1  

If this really is an interview question to test deep understanding of Ruby features, then the answer is trivial: Ruby doesn't have class methods. Nor does it have static methods. It most certainly doesn't have static class methods. If you have a deep understanding of Ruby features, heck, even if you have just a superficial familiarity with Ruby, you know this. Ergo, it's a trick question.

如果这确实是一个面试问题来测试对Ruby特性的深刻理解,那么答案是微不足道的:Ruby没有类方法。它也没有静态方法。它肯定没有静态类方法。如果你对Ruby特性有深刻的理解,那么,即使你只是对Ruby有一种肤浅的熟悉,你也知道这一点。这是一个棘手的问题。