博客地址 :《Ruby 元编程》读书笔记 (十四)
相关文章:
《Ruby 元编程》读书笔记 (一) 《Ruby 元编程》读书笔记 (二) 《Ruby 元编程》读书笔记 (三) 《Ruby 元编程》读书笔记 (四) 《Ruby 元编程》读书笔记 (五) 《Ruby 元编程》读书笔记 (六) 《Ruby 元编程》读书笔记 (七) 《Ruby 元编程》读书笔记 (八) 《Ruby 元编程》读书笔记 (九) 《Ruby 元编程》读书笔记 (十) 《Ruby 元编程》读书笔记 (十一) 《Ruby 元编程》读书笔记 (十二) 《Ruby 元编程》读书笔记 (十三)
传神的头图↓↓↓
前面已经对元编程的理论知识走了一遍,后面的内容侧重于实际操作和源码阅读,今天的 Topic 就是由书中第六章中的例子而来。
书中以迭代的形式,引导读者一步步实现一个名为 attr_checked 的类宏,正所谓上帝造人也需要 7 天,创造和生产这件事儿,过程和迭代都是少不了的,本文完全遵照 guide 而来,美中不足的地方是没有添加,测试用例,不过每个不走都给出了一些简单的实例演示代码。
写一个操作方法类似attr_accessor
的attr_checked
的类宏,该类宏用来对属性值做检验,使用方法如下:
class Person
include CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
me = Person.new
me.age = 39 #ok
me.age = 12 #抛出异常
add_checked_attribute
的内核方法,为指定类添加经过简单校验的属性def add_checked_attribute(klass, attribute)
eval "
class #{klass}
def #{attribute}=(value)
raise 'Invalid attribute' unless value
@#{attribute} = value
end
def #{attribute}()
@#{attribute}
end
end
"
end
add_checked_attribute(String, :my_attr)
t = "hello,kitty"
t.my_attr = 100
puts t.my_attr
t.my_attr = false
puts t.my_attr
这一步使用eval
方法,用class
和def
关键词分别打开类,且定义了指定的属性的 get 和 set 方法,其中的 set 方法会简单的判断值是否为空 (nil 或 false),如果是则抛出Invalid attribute
异常。
def add_checked_attribute(klass, attribute)
klass.class_eval do
define_method "#{attribute}=" do |value|
raise "Invaild attribute" unless value
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
这一步更换掉了eval
方法,同时也分别用class_eval
和define_method
方法替换了之前的class
与def
关键字,实例变量的设置和获取分别改用了instance_variable_set
和instance_variable_get
方法,使用上与第一步没有任何区别,只是一些内部实现的差异。
def add_checked_attribute(klass, attribute, &validation)
klass.class_eval do
define_method "#{attribute}=" do |value|
raise "Invaild attribute" unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
add_checked_attribute(String, :my_attr){|v| v >= 180 }
t = "hello,kitty"
t.my_attr = 100 #Invaild attribute (RuntimeError)
puts t.my_attr
t.my_attr = 200
puts t.my_attr #200
没有什么奇特的,只是加了通过代码块验证,增加了校验的灵活性,不再仅仅局限于 nil 和 false 之间了。
class Class
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise "Invaild attribute" unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
String.add_checked(:my_attr){|v| v >= 180 }
t = "hello,kitty"
t.my_attr = 100 #Invaild attribute (RuntimeError)
puts t.my_attr
t.my_attr = 200
puts t.my_attr #200
这里我们把之前顶级作用域中方法名放到了Class
中,由于所有对象都是Class
的实例,所以这里定义的实例方法,也能被 Ruby 中的其它所有类访问到,同时在 class 定义中,self 就是当前类,所以也就省去了调用类这个参数和class_eval
方法,并且我们把方法的名字也改成了attr_checked
。
module CheckedAttributes
def self.included(base)
base.extend ClassMethods
end
end
module ClassMethods
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise "Invaild attribute" unless validation.call(value)
instance_variable_set("@#{attribute}", value)
end
define_method attribute do
instance_variable_get "@#{attribute}"
end
end
end
class Person
include CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
最后一步通过钩子方法,在CheckedAttributes
模块被引入后,对当前类通过被引入模块进行扩展,
从而使当前类支持引入后的方法调用,即这里的 get 与 set 方法组。
到此,我们已经得到了一个名为attr_checked
,类似attr_accessor
的类宏,通过它你可以对属性进行你想要的校验。
-待续-
=============== 最后贴一下自己的公众账号
可以十日不将军,不可一日不拱卒,日拱一卒 (rigongyizu365)