Ruby 《Ruby 元编程》读书笔记 (十四)

lazybios · 2015年09月21日 · 最后由 lazybios 回复于 2016年06月26日 · 2725 次阅读

博客地址 :《Ruby 元编程》读书笔记 (十四)

相关文章:

《Ruby 元编程》读书笔记 (一) 《Ruby 元编程》读书笔记 (二) 《Ruby 元编程》读书笔记 (三) 《Ruby 元编程》读书笔记 (四) 《Ruby 元编程》读书笔记 (五) 《Ruby 元编程》读书笔记 (六) 《Ruby 元编程》读书笔记 (七) 《Ruby 元编程》读书笔记 (八) 《Ruby 元编程》读书笔记 (九) 《Ruby 元编程》读书笔记 (十) 《Ruby 元编程》读书笔记 (十一) 《Ruby 元编程》读书笔记 (十二) 《Ruby 元编程》读书笔记 (十三)

传神的头图↓↓↓

Ruby元编程

前面已经对元编程的理论知识走了一遍,后面的内容侧重于实际操作和源码阅读,今天的 Topic 就是由书中第六章中的例子而来。

书中以迭代的形式,引导读者一步步实现一个名为 attr_checked 的类宏,正所谓上帝造人也需要 7 天,创造和生产这件事儿,过程和迭代都是少不了的,本文完全遵照 guide 而来,美中不足的地方是没有添加,测试用例,不过每个不走都给出了一些简单的实例演示代码。

任务描述

写一个操作方法类似attr_accessorattr_checked的类宏,该类宏用来对属性值做检验,使用方法如下:

class Person
  include CheckedAttributes

  attr_checked :age do |v|
    v >= 18
  end
end

me = Person.new
me.age = 39  #ok
me.age = 12  #抛出异常

实施计划

  1. 使用 eval 方法编写一个名为add_checked_attribute的内核方法,为指定类添加经过简单校验的属性
  2. 重构 add_checked_attribute 方法,去掉 eval 方法,改用其它手段实现
  3. 添加代码块校验功能
  4. 修改 add_checked_attribute 为要求的 attr_checked,并使其对所有类都可用
  5. 通过引入模块的方式,只对引入该功能模块的类添加 attr_checked 方法

Step 1

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方法,用classdef关键词分别打开类,且定义了指定的属性的 get 和 set 方法,其中的 set 方法会简单的判断值是否为空 (nil 或 false),如果是则抛出Invalid attribute异常。

Setp 2

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_evaldefine_method方法替换了之前的classdef关键字,实例变量的设置和获取分别改用了instance_variable_setinstance_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的类宏,通过它你可以对属性进行你想要的校验。

-待续-

=============== 最后贴一下自己的公众账号

可以十日不将军,不可一日不拱卒,日拱一卒 (rigongyizu365)

赞这个系列,楼主继续加油!

lazybios 《Ruby 元编程》读书笔记 (十六)[连载完结] 提及了此话题。 06月26日 10:50
lazybios 《Ruby 元编程》读书笔记 (十五) 提及了此话题。 06月26日 10:50
需要 登录 后方可回复, 如果你还没有账号请 注册新账号