zoukankan      html  css  js  c++  java
  • 3-16 提取任务(第6章)

    The boss's mission:

    写一个类宏,功能与attr_accessor类似,但会创建经过校验的属性,名字attr_checked。

    需求:

    1. 接受属性名,和block。block用于校验属性,如果对一个属性赋值,非true就报错。
    2. 只给特定的类用,所以不要放到标准库中。只有当类加了CheckedAttributes模块,才拥有这个功能。

    A Development Plan:

    开发计划:

    1. 使用eval方法快速编写内核方法add_checked_attribute,用来为类添加一个校验属性。
    2. 重构这个方法,不用eval.
    3.  通过代码块来校验属性。
    4. 把这个方法修改为名为attr_checked的类宏,让它对所有类可用。
    5. 写一个模块,通过hook method为指定的类添加attr_checked方法。

    第一步 

    创建2个拟态方法,读/写 方法。在写方法加入校验属性值是否nil/false.

    require 'test/unit'
    class Person; end
    class TestCheckedAttribute < Test::Unit::TestCase
      def setup
        add_checked_attribute(Person, :age)
        @bob = Person.new
      end
      def test_accepts_valid_values
        @bob.age = 20
        assert_equal 20, @bob.age
      end
      def test_refuses_nil_values
        assert_raises RuntimeError, 'Invalid attribute' do
          @bob.age = nil
        end
      end
      def test_refuses_false_values
        assert_raises RuntimeError, 'Invalid attribute' do
          @bob.age = false
        end
      end
    end
    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)

    第二步 ,重构

     防止代码外泄后,被攻击,另外增强代码可读性,所以不用eval。标准库里寻找替代方法,注意scope。使用class_eval重新定义类。

    def add_checked_attribute(klass, attribute)
      klass.class_eval do
        ...
      end
    end

    定义读写方法,不能用def关键字,改用动态方法传递参数。

    另外,不能使用"@#{attribute}" =  value 这种字符串给实例变量赋值了。改用其他method,Object#instance_variable_set().

    结果:

    def add_checked_attribute(klass, attribute)
      klass.class_eval do
        define_method("#{attribute}=") do |value|
          raise 'Invalid attribute' unless value
          instance_variable_set("@#{attribute}", value)
        end
        define_method attribute do
          instance_variable_get("@#{attribute}")
        end
      end
    end

     第三步

     增加block验证,测试加入代码块条件,{|v| v >= 18 }

     定义add_checked_attribute方法增加Proc参数, &validation

     在raise上修改为Proc.call(value) ,Invokes the block.

    class TestCheckedAttribute < Test::Unit::TestCase
      def setup
        add_checked_attribute(Person, :age) {|v| v >= 18 }
        @bob = Person.new
      end
    ...
      def test_refuses_invalid_values
        assert_raises RuntimeError, 'Invalid attribute' do
          @bob.age = 17
        end
      end
    end
    def add_checked_attribute(klass, attribute, &validation)
      klass.class_eval do
        define_method("#{attribute}=") do |value|
          raise 'Invalid attribute' unless validation.call(value)
          instance_variable_set("@#{attribute}", value)
        end
    ...

     第四步,校验过的属性。

     把内核方法add_checked_attribute改造成类宏attr_checked,放到类Person中。 

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

    如果让 attr_checked 对所以方法可用,可以定义为Class或Module的实例方法。

    Person的类是Class。Person作为Class的对象可以调用attr_checked方法 .

    这样就不需要class_eval打开类了。因为attr_checked执行时要定义的类self就是Person. 

    class Class
      def add_checked(attribute, &validation)
        define_method("#{attribute}=") do |value|
          raise 'Invalid 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(Person, :age) {|v| v >= 18 } 


     

    第 5 步 hook methods

    定义一个模块,包含include到Person中。 

      module ClassMethods
        def attr_checked(attribute, &validation)
          define_method("#{attribute}=") do |value|
            raise 'Invalid attribute' unless validation.call(value)
            instance_variable_set("@#{attribute}", value)
          end
          define_method attribute do
            instance_variable_get("@#{attribute}")
          end
        end
      end

    但是, 我们的目的是做出atrr_checked,这是是类宏,类方法。

    而类包含的方法都是实例方法,Person.attr_checked是❌的。

    所以想办法让attr_checked成为Person的类方法。

    这里使用Hook methods. Object#included.

    1. 当Person包含模块CheckAttributes时,会自动调用钩子方法included.
    2. 这个钩子方法传递参数就是类的名字,使用extend方法进行类扩展
    3. extend方法把模块ClassMethods中的方法attr_checked包含到Person的单件类中。
    所以,attr_checked就成了类方法,可以被类直接使用了,模拟了类宏。

    ⚠️ :这里attr_checked内生成的动态方法,是类Person的实例方法。

    类方法的定义 有三种。分别是

    • def 类名.method;end
    • 在类中,用def self.method;end
    • 在类中,用class << self...end

     代码:

    module CheckedAttributes
      def self.included(base)
        base.extend ClassMethods
      end
      module ClassMethods
        def attr_checked(attribute, &validation)
          define_method("#{attribute}=") do |value|
            raise 'Invalid attribute' unless validation.call(value)
            instance_variable_set("@#{attribute}", value)
          end
          define_method attribute do
            instance_variable_get("@#{attribute}")
          end
        end
      end
    end

     小结 :Wrap_up

    这是一个有难度的元编程问题,编写了自己定义的类宏。

    最终的全代码 

    require 'test/unit'
    class TestCheckedAttribute < Test::Unit::TestCase
      def setup
        @bob = Person.new
      end
      def test_accepts_valid_values
        @bob.age = 20
        assert_equal 20, @bob.age
      end
      def test_refuses_invalid_values
        assert_raises RuntimeError, 'Invalid attribute' do
          @bob.age = 17
        end
      end
    end
    module CheckedAttributes
      def self.included(base)
        base.extend ClassMethods
      end
      module ClassMethods
        def attr_checked(attribute, &validation)
          define_method("#{attribute}=") do |value|
            raise 'Invalid attribute' unless validation.call(value)
            instance_variable_set("@#{attribute}", value)
          end
          define_method attribute do
            instance_variable_get("@#{attribute}")
          end
        end
      end
    end
    class Person
      include CheckedAttributes
      self.attr_checked( :age ) do |v|
        v >= 18
      end
    end
  • 相关阅读:
    vue中使用vw适配移动端
    在vue项目中使用scss
    前端用vue怎么接收并导出文件
    QT编辑
    前置声明
    morphologyEx() getStructuringElement()
    # 类定义中调用另一个类函数的方式
    指针
    NEW
    DECLARE_DYNAMIC IMPLEMENT_DYNAMIC
  • 原文地址:https://www.cnblogs.com/chentianwei/p/8583607.html
Copyright © 2011-2022 走看看