ruby元编程第一部分

1. 打开类

可以重新打开已经存在的类(即使你不是这个类的创建者)并对之进行动态修改或者扩展功能,即使像Array和String这样标准库的类也不例外,这种行为称之为打开类

假设你想创建一个方法,功能是接受一个字符串参数,只保留字符串中数字和字母,代码如下

class String
    def to_alphanumeric
        #\w匹配单词,等同于 /[A-Za-z0-9_]/ 
        #\s匹配空格,等同于 /[ \t\r\n\f]/
        gsub(/[^\w\s]/, '')
    end
end
 
puts "A&^ar$o%n&* (is&*&))) t&*(*he B0&*S**^S)".to_alphanumeric
# => "Aaron is the B0SS"

2.猴子补丁

如果你粗心地为某个类添加了新功能,同时覆盖了类原来的方法,进而影响到其他部分的代码,这种修改类的处理方法称之为猴子补丁(Monkeypatch)
我们写一个方法Array#replace(使用新字符替换数组中某个字符),但是Array类已经有replace方法(功能是用一个作为参数传入的数组替换当前数组的全部内容),这样就会把原有的方法覆盖掉,不太好,可能我们原本没打算覆盖原有的功能,代码如下


class Array
    def replace(original, replacement)
        self.map {|e| e == original ? replacement : e }
    end
end
 
puts ['x', 'y', 'z'].replace('x', 'a')
 
# => a, y, z

3.对象模型

对象,类与模块的关系

这看起来像一个复杂的图表,但是它清晰的表示出了 Ruby 中对象,类,模块之间的联系。有三个要点。

  1. 实例化的对象 (obj1,obj2,obj3) 是 MyClass 类的对象。
  2. MyClass 是属于 Class 的对象(这在 Ruby 里也是对象的意思,我知道,这让你很费解)。
  3. MyClass 是 Class 的类对象,同时它也继承自 Object。
    我们还会在第二部分引用这个,现在我们继续看继承链。

继承链

继承和模块的关系

当你调用一个方法时,Ruby 向右进入的接收者类,然后向上遍历继承链,直到找到被调用方法或者抵达尽头
在上图b是对象Book类的一个实例对象,Book类包含两个模块Printable 和 Document.同时Book类继承自Object类(ruby中几乎一切事物的基类),Object类又包含Kernel模块,同时继承自BasicObject类(ruby中一切对象的绝对父类)

prepend、include与祖先链
祖先链用于描述Ruby对象的继承关系,因为类与模块是父子关系,所以祖先链中也可以包含模块,prepend与include分别可以向链中添加模块,不同的是调用include方法,模块会被插入祖先链,当前类的正上方,而prepend同样是插入到祖先链,但位置却在当前类的正下方,另外通过Class.ancestors可以查看当前的祖先链

4. 动态定义方法(Module#define_method)

通过define_method方法取代了关键词def,其本质上都是相同的,只是在定义方式上,define_method的方式更加灵活一些,可以通过在编码中通过推导,完成函数的定义,增加了实现的灵活性。
示例代码如下:

class MyClass
    define_method :my_method do |my_arg|
        my_arg * 3
    do
end

obj = MyClass.new
obj.my_method(2)  #=> 6

ActivateRecord(rails中的默认ORM工具)中大量的使用了这种特性,例如

class Book < ActiveRecord::Base
end
 
b = Book.new
b.title

假定 Book 类是一个 Book 数据库表的一个 ORM 包装器,并且 title 就是这个表里的字段,那么我们就能得到由 b 表示的那个数据库里的 title 所指定的那一列。

原理是:即使我们在Book类中并没有定义title属性,当这个类的实例对象调用这个方法时应该报NoMethodError错误,但是ActiveRecord会动态添加这个方法,就和我们手动添加的一样
ActiveRecord源码就是一个把元编程运用到极致的例子

5. 动态调用方法(Object#send)

在Ruby中通过Object#send方法可以代替点标识调用对象的指定实例方法,好处是可以在编码中动态的决定方法的调用

由于在ruby中几乎所有的类都继承自Object对象,所以可以在任意对象上调用send方法来访问这个对象上的所有方法和属性.Object也允许你调用私有属性,但如果你的本意不是这样就需要注意,可以使用Object#public_send这种方式,作用与直接访问相同只是限制不能访问私有方法或私有成员
代码示例如下:

class MyClass
    def my_method(my_arg)
        my_arg * 2
    end
end

obj = MyClass.new
obj.my_method(3)    #=> 6
obj.send(:my_method, 3) #=> 6
%w(test1 test2 test3 test4 test5).each do |s|
    # define_method动态定义方法
    define_method(s) do
        puts "#{s} was called"
    end
end
 
# send动态调用方法
(1..5).each { |n| send("test#{n}") }
 
# => test1 was called
# => test2 was called
# => test3 was called
# => test4 was called
# => test5 was called

6. 动态删除方法(undef,undef_method和remove_method)

首先undef是关键字,remove_method和undef_method是方法
undef关键字和undef_method方法的作用一样
remove_method 移除当前类中的方法定义,当前类仍然能使用从父类继承过来的同名方法
undef_method 移除当前类中的方法定义,并阻止使用从父类继承过来的同名方法
2者对非当前类定义的方法没有影响,特别父类中定义的同名方法

class C
  def say_hello
    puts "hello C!"
  end
end

class C1 < C
  def say_hello
    puts "hello C1!"
  end
end

C.new.say_hello #hello C!
C1.new.say_hello #hello C1!


class C1 < C
  remove_method :say_hello
end

C.new.say_hello #hello C!
C1.new.say_hello #hello C! 注意是C不是C1

class C1 < C
  undef_method :say_hello
end

C.new.say_hello #hello C!
C1.new.say_hello #undefined method say_hello!

7. 魔术方法(BasicObject#method_missing)

可以创建一个在无此方法错误事件(但在之前是会有错误)前被自动触发调用的处理程序

method_missing利用的机制是,当一个对象进行某个方法调用的时候,会到其对应的类的实例方法中进行查找,如果没有找到,则顺着祖先链向上查找,直到找到BasicObject类为止。如果都没有则会最终调用一个BasicObject#method_missing抛出NoMethodError异常。

与method_missing类似,还有关于常量的const_missing方法,当引用一个不存在的常量时,Ruby会把这个常量名作为一个符号传递给const_missing方法。

当我们需要定义很多相似的方法时候,可以通过重写method_missing方法,对相似的方法进行统一做出回应,这样一来其行为就类似与调用定义过的方法一样。
代码示例如下:

class Roulette
  def method_missing(name, *args)
    person = name.to_s.capitalize
    #如果不是这几个方法,走BasicObject类定义的method_missing方法
    super unless %w[Bob Frank Bill Honda Eric].include? person
    number = 0
    3.times do
      number = rand(10) + 1
      puts "#{number}..."
    end
    "#{person} got a #{number}"
  end
end

number_of = Roulette.new
puts number_of.bob
puts number_of.kitty

7. 补充:

动态方法与Method_missing的使用原则

当可以使用动态方法时候,尽量使用动态方法。除非必须使用method_missing方法(方法特别多的情况),否则尽量少使用它。

类与模块

在ruby中类与模块的区分不是太明显,完全可以将二者相互替代,之所以同时保留二者的原因是为了保持代码的清晰性,让代码意图更加明确。使用原则:

希望把自己代码包含(include,prepend)到别的代码中,应该使用模块
希望某段代码被实例化或被继承,应该使用类

模块机制可以用来实现类似其他语言中的命名空间概念

::符号

双冒号是定义 namespace 用的,或者叫 scope
当你使用 Foo::Bar 的时候,实际你是在找一个名字叫 Foo 的 namespace ,然后让它返回它里面的 Bar 参数 , 这个 Bar 可以是个常量,可以是个类,可以是个方法 (后两者在 Ruby 中可视为常量)
同理使用 FooBar::method1 的时候实际上是在要求返回 FooBar 这个 namespace 中 method1 这个「常量」的值。
使用 FooBar.method1 的时候则是在调用这个方法,当然返回结果是一样的,这里 :: 和 . 确实是可以互换不影响结果。但 :: 只能用来找 class method , instance method 就只能用 .
另外 :: 还能用来找真正的常量,比方这样

class Foo
  Bar = "hello"
  bar = "hello"
end

Foo::Bar  # => "hello"
Foo::bar  #  => 出错
Foo.Bar  #  => 出错
Foo.bar  #  => 出错

另外 :: 在开始位置则表示回到 root namespace ,就是不管前面套了几个 Module ,都算你其实写在最外层。

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

推荐阅读更多精彩内容

  • 对象模型 所有class定义之外的代码默认运行在顶级对象main中。 打开类 ruby的class更像是一个作用于...
    五月的约修亚阅读 3,474评论 0 4
  • 面试中碰到一些面试官提到的关于ruby元编程的问题的总结 class和module的区别 官方说明class do...
    auguszou阅读 386评论 0 1
  • 类 方法 代码块 类宏 Eval方法 实例变量、方法、类 实例变量(Instance Variables)是当你使...
    youngiii阅读 1,101评论 0 51
  • 类定义揭秘 当前类小结 在类定义中,当前对象self就是正在定义的类。 Ruby解释器总是追踪当前类(模块)的引用...
    CharlesZhangCh阅读 277评论 0 0
  • 我无法给你像样的爱情,抱歉 我也不配 我应该抓紧时间去做点自己感兴趣的事,毕竟人生不长。 世间并没有所谓的坚持。看...
    等待戈多nl阅读 119评论 0 0