Ruby中钩子方法的运用实例解析
通过使用钩子方法,可以让我们在Ruby的类或模块的生命周期中进行干预,可以极大的提高编程的灵活性。
与生命周期相关的钩子方法有下面这些:
类与模块相关
- Class#inherited
- Module#include
- Module#prepended
- Module#extend_object
- Module#method_added
- Module#method_removed
- Module#method_undefined
单件类相关
- BasicObject#singleton_method_added
- BasicObject#singleton_method_removed
- BasicObject#singleton_method_undefined
示例代码
moduleM1
defself.included(othermod)
puts“M1wasincludedinto#{othermod}”
end
end
moduleM2
defself.prepended(othermod)
puts“M2wasprependedto#{othermod}”
end
end
classC
includeM1
includeM2
end
#输出
M1wasincludedintoC
M2wasprependedtoC
moduleM
defself.method_added(method)
puts“Newmethod:M##{method}”
end
defmy_method;end
end
#输出
Newmethod:M#my_method
除了上面列出来的一些方法外,也可以通过重写父类的某个方法,进行一些过滤操作后,再通过调用super方法完成原函数的功能,从而实现类似钩子方法的功效,如出一辙,环绕别名也可以作为一种钩子方法的替代实现。
运用实例
任务描述:
写一个操作方法类似attr_accessor的attr_checked的类宏,该类宏用来对属性值做检验,使用方法如下:
classPerson includeCheckedAttributes attr_checked:agedo|v| v>=18 end end me=Person.new me.age=39#ok me.age=12#抛出异常
实施计划:
使用eval方法编写一个名为add_checked_attribute的内核方法,为指定类添加经过简单校验的属性
重构add_checked_attribute方法,去掉eval方法,改用其它手段实现
添加代码块校验功能
修改add_checked_attribute为要求的attr_checked,并使其对所有类都可用
通过引入模块的方式,只对引入该功能模块的类添加attr_checked方法
Step1
defadd_checked_attribute(klass,attribute)
eval"
class#{klass}
def#{attribute}=(value)
raise'Invalidattribute'unlessvalue
@#{attribute}=value
end
def#{attribute}()
@#{attribute}
end
end
"
end
add_checked_attribute(String,:my_attr)
t="hello,kitty"
t.my_attr=100
putst.my_attr
t.my_attr=false
putst.my_attr
这一步使用eval方法,用class和def关键词分别打开类,且定义了指定的属性的get和set方法,其中的set方法会简单的判断值是否为空(nil或false),如果是则抛出Invalidattribute异常。
Setp2
defadd_checked_attribute(klass,attribute)
klass.class_evaldo
define_method"#{attribute}="do|value|
raise"Invaildattribute"unlessvalue
instance_variable_set("@#{attribute}",value)
end
define_methodattributedo
instance_variable_get"@#{attribute}"
end
end
end
这一步更换掉了eval方法,同时也分别用class_eval和define_method方法替换了之前的class与def关键字,实例变量的设置和获取分别改用了instance_variable_set和instance_variable_get方法,使用上与第一步没有任何区别,只是一些内部实现的差异。
Step3
defadd_checked_attribute(klass,attribute,&validation)
klass.class_evaldo
define_method"#{attribute}="do|value|
raise"Invaildattribute"unlessvalidation.call(value)
instance_variable_set("@#{attribute}",value)
end
define_methodattributedo
instance_variable_get"@#{attribute}"
end
end
end
add_checked_attribute(String,:my_attr){|v|v>=180}
t="hello,kitty"
t.my_attr=100#Invaildattribute(RuntimeError)
putst.my_attr
t.my_attr=200
putst.my_attr#200
没有什么奇特的,只是加了通过代码块验证,增加了校验的灵活性,不再仅仅局限于nil和false之间了。
Step4
classClass
defattr_checked(attribute,&validation)
define_method"#{attribute}="do|value|
raise"Invaildattribute"unlessvalidation.call(value)
instance_variable_set("@#{attribute}",value)
end
define_methodattributedo
instance_variable_get"@#{attribute}"
end
end
end
String.add_checked(:my_attr){|v|v>=180}
t="hello,kitty"
t.my_attr=100#Invaildattribute(RuntimeError)
putst.my_attr
t.my_attr=200
putst.my_attr#200
这里我们把之前顶级作用域中方法名放到了Class中,由于所有对象都是Class的实例,所以这里定义的实例方法,也能被Ruby中的其它所有类访问到,同时在class定义中,self就是当前类,所以也就省去了调用类这个参数和class_eval方法,并且我们把方法的名字也改成了attr_checked。
Step5
moduleCheckedAttributes
defself.included(base)
base.extendClassMethods
end
end
moduleClassMethods
defattr_checked(attribute,&validation)
define_method"#{attribute}="do|value|
raise"Invaildattribute"unlessvalidation.call(value)
instance_variable_set("@#{attribute}",value)
end
define_methodattributedo
instance_variable_get"@#{attribute}"
end
end
end
classPerson
includeCheckedAttributes
attr_checked:agedo|v|
v>=18
end
end
最后一步通过钩子方法,在CheckedAttributes模块被引入后,对当前类通过被引入模块进行扩展,从而使当前类支持引入后的方法调用,即这里的get与set方法组。
到此,我们已经得到了一个名为attr_checked,类似attr_accessor的类宏,通过它你可以对属性进行你想要的校验。