Ruby中Class,Object,Module这三个类定义了一些回调方法,这些方法不是默认的不过为一个类模块获对象定义了这些方法,当特定的事件发生时,将调用这些回调和钩子方法。这些回调方法一般以"ed"结尾。特定事件是指:
调用一个不存的对象方法(method_missing)
模块被混含(included/extended)
类被继承(inherited)
类或模块定义实例方法(method_added)
对象新增加一个单例方法(singleton_method_added)
实例方法被删除或取消(method_removed/method_undefined)
对象的单例方法被取消或被取消(singleton_method_removed/singleton_undefined)
引用一个不存在的常量(const_missing)
以上每一个事件对应的方法被定义了,当事件发生时,这些方法将会被调用。
method_missing
给对象调用一个方法,对象未定义,则会抛出NoMethodError异常,这时就可以使用method_missing来拦截,该方法接受为一个一个参数,即被调用的未定义方法的方法名。
- class C
- def method_missing(m)
- puts "There's no method called #{m} here -- please try again."
- end
- end
- C.new.anything
这里就会调用method_missing方法。
method_missing是一个有用的工具,在所有Ruby标准的钩子方法和回调方法中,是使用最广泛的一个回调方法。
Module#included
当一个模块混入到类时,如果该模的included方法已经定义,那么该方法被调用。该方法的唯一参数就是接受混含的类的名字。
- module M
- def self.included(c)
- puts "I have justbeen mixed into #{c}."
- end
- end
- class C
- include M
- end
混含模块到类中使得在模块中的所有实例方法可以被作为类的实例的方法。如果在混含模块时要给包含此模块的类添加类方法不能采用形如:def self.method_name end形式,可以用included回调方法。
用included可以捕获混含操作,并以此给混含模块的类添加类方法:
- module M
- def self.included(c)
- def c.a_class_method
- puts "Now the class has a new class method."
- end
- end
- end
-
- class C
- include M
- end
- c = C.new
- C.a_class_method
输出结果:
Now the class has a new class method.
由于C做为included的唯一参数传入到c,接着定义了c.a_class_method就相当于是给类C定义了类方法,extended方法与此类似。
有的人喜欢以此种方式给模块添加实例及类方法:
在这之前不得不说说include与extend的区别。include是个私有方法,顾名思义是包含的意思,也即把模块中的方法包含进来。在ruby中方法都是存储在类中的,一个对象调用一个方法是在所属类的继承链中由近及远(包括单例类)的寻找,直到找到为止。所以include方法只能由类对象来本身来调用,而不能由普通实例(因为其实包括Class类在内的所有类都是Class类的实例,所以我称类这种特殊的对象为类对象),并且include方法是私有的,所以只能在类中隐式调用。正因为ruby中方法是保存在类中,所以也只能由类包含(include)方法了。而类调用include的结果是产生了一个委托类,作为调用include方法的这个类的直接父类。而原来模块中的方法,都会塞到委托类中而被此类继承成为实例方法。
而extend顾名思义,是拓展的意思,即拓展一个对象。因为类也是对象,所以类对象,普通对象都可以调用这个extend,也即extend是个public方法。我们知道拓展一个对象就是往这个对象的单例类中塞东西,包括类对象在内的每个对象都可以有一个单例类,单例类会成为原先产生这个对象的那个类的直接子类,但是单例类是匿名的并且对外部是透明的。extend一个模块,就是把一个模块中的方法塞到这个对象的单例类中。当一个类对象调用extend方法时,模块中的方法就会变成类对象的单例类中的实例方法,既然是类对象所属的单例类中的方法,那就由类直接调用咯,既然是由类直接调用那么也就是类方法了。其实类方法也没有太多神奇的地方,也是实例方法。前面说过方法都是存储在类中的,那类方法呢?因为类也是对象,所以类方法也是存储在类中,只不过存储在类对象的单例类中。我们知道所有的类都是Class类(当然也包括Class对象本身)的实例,那么类的单例类就会变成Class类的直接子类,只不过类的单例类有些特殊之处就是类的单例类之间会保持原来类的继承性,即如果原来两个类有继承关系,那么这两个类的单例类也会有相应的继承关系,当然这一切对外部而言还是透明的。
- module M
-
- self.included(recipient)
-
- recipient.extend(ModuleClassMethods)
-
- recipient.class_eval do
-
- include ModuleInstanceMethods
-
- end
-
- end
-
- module ModuleClassMethods
-
-
-
- end
-
- module ModuleInstanceMethods
-
-
-
- end
-
- end
-
method_add
- class A
- def self.method_added(name)
- puts "#{name} is a new method"
- end
- end
-
- class A
- def a
- end
- end
- a is a new method
此方法接受一个参数为新定义方法的方法名,若定义了此方法,以后每次为这个类增加一个新方法时都会调用method_add方法,类似的当定义了singleton_method_added方法时为对象添加单例方法时会被调用,对于类对象来说单例方法就是类方法。
Class#inherited
如果为给定的类定义了inherited方法,那么在为它生成子类时,inherited会被调用,唯一的调用参数是新的子类的名字:
- class C
- def self.inherited(subclass)
- puts "#{self} just got subclassed by #{subclass}"
- end
- end
- class D < C
- end
D继承C,触发了inherited方法,输出结果:
C just got subclassed by D
Module#const_missing
在给定的模块或类中引用一个不可识别的常量时,该方法被调用:
- class C
- def self.const_missing(const)
- puts "#{const} is undefined-setting it to 1."
- const_set(const, 1)
- end
- end
- puts C::A
- puts C::A
输出结果:
A is undefined-setting it to 1.
1
1
附:rails activerecord回调方法(以创建新记录为例,更新记录也类似将下列create改为update即可):
- save
- valid?
- (1) before_validation
- (2) before_validation_on_create
- validate
- validate_on_create
- (3) after_validation
- (4) after_validation_on_create
- (5) before_save
- (6) before_create
- 数据库插入
- (7) after_create
- (8) after_save
共8个回调方法为上述标号(1)至(8)