元编程:编写代码的代码

知识点一: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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,018评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,046评论 2 372
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,215评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,303评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,181评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,171评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,577评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,260评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,546评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,630评论 2 311
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,390评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,260评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,633评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,920评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,210评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,569评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,767评论 2 335

推荐阅读更多精彩内容

  • //Clojure入门教程: Clojure – Functional Programming for the J...
    葡萄喃喃呓语阅读 3,604评论 0 7
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,505评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 今天开始,骑行90(100)公里,环青海湖。至于环湖这个念头怎么来的,我还真不知道,就那么一瞬间蹦出来的,为了促使...
    梦蛟龙阅读 231评论 0 0
  • 这些年,我就这样过去了,一个人过去了。那些曾经伴我成长的你们,最终还是离开了。到头来我还是孤身一人。如果说毕...
    橙子焦糖你阅读 102评论 0 1