module Imodule
???
end
class Some
include Imodule
def self.imethod
puts "original"
end
end
Some.imethod
# => "overrided"
如何创建一个覆盖静态方法的模块?
这是一个深入了解ruby功能的访谈问题。不建议另一个问题的表述:)
答案 0 :(得分:30)
好的,这是一个有效的代码。请注意,您甚至不必触及目标类! :)
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
,它将打印“module”。但我们不想触及原始类定义,我们希望从外部改变它。
对我们有好处,红宝石有一个"open classes"的概念。这意味着我们几乎可以更改应用程序中的所有内容,甚至是某些第三方库。每个类都可以“打开”,并且可以向其添加新方法,或者可以重新定义旧方法。让我们来看看它是如何工作的。
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创建类方法。
这取自我的blog post。
答案 1 :(得分:4)
如果您需要在新方法中调用刚刚覆盖的原始方法,该怎么办?
换句话说,如果您希望能够从覆盖的类方法调用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 singleton methods with (class_eval, define_method) vs (instance_eval, define_method)和http://yugui.jp/articles/846教育我并指出我正确的方向。
答案 2 :(得分:1)
如果这个真的是一个面试问题来测试对Ruby特性的深刻理解,那么答案是微不足道的:Ruby没有类方法。它也没有静态方法。它肯定没有静态类方法。如果您对Ruby功能有深入的了解,那么,即使您只是对Ruby有一种肤浅的熟悉,您也知道这一点。这是一个棘手的问题。
答案 3 :(得分:0)
覆盖类方法的最简单方法是将模块prepend
到singleton
的Some
类:
module Imodule
def imethod
puts "overrided"
end
end
Some.singleton_class.prepend(Imodule)
Some.imethod
# => "overrided"
说明:extend
对类方法的作用与 include
对实例方法的作用相同。 extend
和 include
都在类主体中声明的方法之前解析,因此它们永远不会被覆盖。
为了覆盖实例方法,我们使用 prepend
但是对于类方法没有 prepend
的替代方案。由于在 Ruby 中一切都是对象,因此您可以做的是在将类视为对象实例时使用 prepend
。因为类方法本质上是在对象单例类中声明的对象实例方法,所以可以打开对应的单例类,prepend
方法。
你也可以在课堂上做同样的事情:
class Some
class << self
prepend Imodule
end
end
Some.imethod
# => "overrided"