问题
在 Ruby 中,我们都知道 include 能引入一个模块的方法作为当前类/模块的实例方法,以及 extend 可以引入模块的方法作为当前类/模块的类方法(单例方法)。那么模块中本身的类方法(单例方法)呢?该怎么引入他呢?
首先两种直接引入的方式都是不行的:
module M
def instance_m; end
def self.class_m; end # 模块中的类方法无法被引入
end
class C
include M
extend M
end
C.instance_m #=> nil
C.class_m #=> NoMethodError
理解
先抛结论:在模块中直接定义的类方法(self.m)是无法被其他类/模块引入使用的。
原因的话,引入(include)做的事是只会操作当前模块的方法(实例方法),而模块的类方法实际上是属于模块的单例类(singleton class)的方法,关于单例类的解释,这个链接里解释的很好: What exactly is the singleton class in ruby?
解决
上述结论里特意指出“直接定义”,所以这个问题是可以通过其他方式解决的,而且解决方案被广大库(Rails等)推崇。因此我们以后设计模块的时候也应该考虑该设计。
module M
# 将单例方法换一种方式定义,定义在子模块中,模块名有时也被写作(SingletonMethods)
module ClassMethods
def class_m; end
end
# 自身用引入该模块的方法为类方法,实现直接定义单例方法相同的效果(self.class_m)
extend ClassMethods
# 通过钩子,在其他类/模块引入该模块时,同时引入该模块的类方法(作为操作类/模块的类方法引入)
def self.included(base)
base.extend ClassMethods
end
end
之后,只要把原来直接定义的类方法(self.m)都定义到 ClassMethods 子模块中,就行了。
Rails 的 ActiveSupport::Concern 对上述的操作进行了一些简单封装,使得操作更加方便。