元编程:编写代码的代码

知识点一:Binding类
Binding通过创建绑定对象来捕获并带走当前的作用域,通过Kernel#binding方法进行实现

class MyClass
  def my_method
    @x = 1
    binding
  end
end

#b就是就是返回的绑定对象,因为方法my_method返回了binding方法,就是返回了绑定对象。
b = MyClass.new.my_method

#eval传递一个Binding对象作为额外的参数,代码就可以在这个Binding对象所携带的作用域中执行
eval ("@x", b)  #=>1

#Ruby还提供了TOPLEVEL_BINDING的预定义常量,它表示顶级作用域的Binding对象:
class AnotherClass
  def my_method
    eval("self", TOPLEVEL_BINDING )
  end
end

puts AnotherClass.new.my_method #=>main

知识点二:eval执行代码字符串
代码字符串:字符串中包含ruby代码
array = [10, 20]
element = 30
puts eval("array << element")  #=>[10, 20, 30]

#eval方法有一个必要参数还有三个可选参数:
eval(statements, @binding, file, line)
#一行字符串形式的ruby代码,@binding表示绑定对象,file表示文件名,line表示行数

#instance_eval()方法也可以执行代码字符串
array = ["a", "b", "c"]
x = "d"
array.instance_eval "self[1] = x"
puts array #=>["a","d","c"]

#eval的代码注入
def explore_array(method)
  code = "['a','b','c'].#{method}"
  puts "evaluating: #{code}"
  eval code
end

loop {p explore_array(gets())}

#如果在终端执行上面的代码,回车输入如下的方法:
length();Dir.glob(*)
#这段代码不仅会显示这个数组的长度,也会显示代码所示文件及所在文件夹的文件

#可以通过动态派发方式替换eval方法:
def explore_array(method, *arguments)
  ['a', 'b', 'c'].send(method, *arguments)
end

explore_array(:length)

#更为合适的方式是使用污染对象和安全级别:
user_input = "User input: #{gets()}"
puts user_input.tainted?

#在如上的代码中,user_input是一个受污染对象。安全级别是通过使用$SAFE局部变量来进行设定的
#如果安全级别大于0,Ruby都会拒绝执行污染的字符串:
$SAFE = 1
user_input = "User input: #{gets()}"
eval user_input

执行结果:insecury operation  SecurityError

知识点三:钩子方法
1.钩子方法:当执行或者调用某个方法的时候,和这个方法相关联的方法会被执行。
module M
  def self.included(othermod)
    puts "#{self} was mixed into #{othermod}"
  end
end

class C
  include M  #=> M was mixed into C
end

2.类扩展:在该类的eigenclass中include模块,使得模块中的实例方法成为类的类方法。
module M
  def demo_method
    puts "this is the demo method in module"
  end
end

class DemoClass
  extend M
end

DemoClass.demo_method #=> this is the demo method in module

3.类扩展混入:钩子方法和类扩展结合。
- 定义一个模块,比如MyMixin
- 在MyMixin中定义一个内部模块,这些方法最终会成为类方法。
- 覆写MyMixin#included()方法来用ClassMethods扩展包含者(使用extend()方法)

module MyMixin
  def self.included(base)
    base.extend(ClassMethods)
    #Object#extend()只是接受者eigenclass中包含模块的快捷方式。
  end

  module ClassMethods
    def demo_method
      puts "this is the method in the module"
    end
  end
end

class DemoClass
  include MyMixin
end

DemoClass.demo_method #=> this is the method in the module


#如果不需要为包含着提供实例方法,可以没有必要提供内部模块,直接在MyMixin中定义方法即可,这个方法会成为被包含着的类方法:
module MyMixin
  def self.included(base)
   base.extend(self)
  end

  def demo_method
    puts "this is the method in the module"
  end
end

class DemoClass
  include MyMixin
end

DemoClass.demo_method #=> this is the method in the module
案例
案例:
class Person
  include CheckedAttributes
  
  attr_checked :age do |v|
    v >= 18
  end
end

me = Person.new
me.age = 39
me.age = 12 #抛出异常
步骤:
1.使用eval方法编写add_checked_attribute的内核方法,为类添加一个简单的经过校验的属性
2.重构add_checked_attribute方法,去掉eval方法
3.通过代码块来校验属性
4.把add_checked_attribute方法修改为attr_checked的类宏,对所有类可用

第一步:写一个基于eval()的方法用来为类添加一个最简单的校验过的属性
require 'test/unit'

class Person; end

class TestCheckedAttribute < Test::Unit::TestCase
  def setup
    #必须是:age的形式
    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(clazz, attribute)
  eval "
    class #{clazz}
      def #{attribute}=(value)
        raise 'Invalid attribute' unless value
        @#{attribute} = value
      end

      def #{attribute}()
        @#{attribute}
      end
    end
  "
end

第二步:去掉eval,使用打开类技术

def add_checked_attribute(clazz, attribute)
  clazz.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

第三步:通过代码块来校验属性
require "test/unit"

class Person
end

class TestCheckedAttribute < Test::Unit::TestCase
  def setup
    add_checked_attribute(Person, :age) { |v| v >= 18 }
    @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 = 17
    end
  end
end

#实现结果
def add_checked_attribute(clazz, attribute, &validation)
  clazz.class_eval do
    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


第四步:把内核方法改造成类宏,对所有的类都可用
require "test/unit"

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

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_nil_values
    assert_raises RuntimeError, "Invalid attribute" do
      @bob.age = 17
    end
  end
end

#实现方式
class Class
  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

第五步:使用类扩展混入技术,包含CheckedAttributes模块可用
require "test/unit"

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

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_nil_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

你可能感兴趣的:(元编程:编写代码的代码)