Ruby中钩子方法的运用实例解析

2019-09-25 09:36:34于丽

这一步更换掉了eval方法,同时也分别用class_eval和define_method方法替换了之前的class与def关键字,实例变量的设置和获取分别改用了instance_variable_set和instance_variable_get方法,使用上与第一步没有任何区别,只是一些内部实现的差异。

Step 3

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之间了。

Step 4

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。

Step 5

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的类宏,通过它你可以对属性进行你想要的校验。