最近睡前把《Ruby元编程》作为枕边书,复习一些元编程技巧。顺手整理记录下这些技巧的同时,也为了这门语言能更广泛地传播,希望有更多的人喜欢Ruby这个神器。
一切都是对象
来到Ruby的世界,请你首先不要被这一切搞晕,实际上这些反而让Ruby的对象模型概念变得更加清晰。一切都是对象,对象是一个类,类也是一个对象。而实际上类是一个带有特殊功能的模块。
1 2 3 4 5 6 7 8 9 10 11
| 2.5.1 :001 > Class.superclass => Module 2.5.1 :002 > Module.superclass => Object 2.5.1 :003 > Object.superclass => BasicObject 2.5.1 :004 > BasicObject.superclass => nil 2.5.1 :005 > Kernel.class => Module
|
模块的超类是BasicObject,BasicObject才是Ruby类系统的根。BasicObject派生了Objce和Kernel,Object又派生出了Module,Module派生出Class。
这像不像道德经里所说的“道生一,一生二,二生三,三生万物”? 再说下去更迷糊了,去Ruby的世界里自己领悟吧。Ruby诞生自亚洲,一直觉得它是一门充满东方哲学的编程语言。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 2.5.1 :001 > Class.ancestors => [Class, Module, Object, Kernel, BasicObject] 2.5.1 :002 > Class.superclass => Module 2.5.1 :003 > Module.ancestors => [Module, Object, Kernel, BasicObject] 2.5.1 :004 > Module.superclass => Object 2.5.1 :005 > Object.ancestors => [Object, Kernel, BasicObject] 2.5.1 :006 > Object.superclass => BasicObject 2.5.1 :007 > BasicObject.superclass => nil 2.5.1 :008 > Kernel.ancestors => [Kernel] 2.5.1 :009 > BasicObject.ancestors => [BasicObject]
|
猴子补丁(Monkey Patch)
在元编程的所有技巧中,首先需要了解的就是Ruby的“猴子补丁”,这是一种打开类的技巧。不同于其他面向对象语言,Ruby的任何类都可以在运行态打“补丁”的,称之为Monkey Patch。
例如,对字符类String,可以在任何地方打开这个类,对它添加一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 2.5.1 :001 > class String 2.5.1 :002?> def test_method 2.5.1 :003?> return self+"AAA" 2.5.1 :004?> end 2.5.1 :005?> end => :test_method 2.5.1 :006 > s="aaa" => "aaa" 2.5.1 :007 > s.class => String 2.5.1 :008 > s.test_method => "aaaAAA" 2.5.1 :009 >
|
在上面的操作中,我们可以看到,对于Ruby的内置类String,我们为其添加了test_method方法,在下文中,任何的String对象便具有了新添加的这个方法,这就是Ruby打开类的魔术。
你甚至可以对内核模块Kernel打一个Monkey Patch,让你的方法在下文中得到全局支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 2.5.1 :001 > test Traceback (most recent call last): 3: from /home/chorder/.rvm/rubies/ruby-2.5.1/bin/irb:11:in <main> 2: from (irb):1 1: from (irb):1:in test ArgumentError (wrong number of arguments (given 0, expected 2..3)) 2.5.1 :002 > module Kernel 2.5.1 :003?> def test 2.5.1 :004?> puts "OK" 2.5.1 :005?> end 2.5.1 :006?> end => :test 2.5.1 :007 > test OK => nil 2.5.1 :008 >
|
不同的是,Kernel是一个模块,需要用module关键字来打开。test方法一开始是不存在的,把它作为“补丁”加入到Kernel模块以后,就可以在下文调用了。
Monkey Patch在有些编程语言里是个贬义词,但在Ruby里,它是一个很实用的功能。
细化(refine)
如你所见,Ruby打开类的作用是如此神奇,以至于Monkey Patch一不小心可能会引发一些大的问题。因此有时候需要配合类的细化功能一起使用。
细化的用法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 2.5.1 :001 > module Refine 2.5.1 :002?> refine String do 2.5.1 :003 > def test 2.5.1 :004?> return self+"TEST" 2.5.1 :005?> end 2.5.1 :006?> end 2.5.1 :007?> end => #<refinement:String@Refine> 2.5.1 :008 > "AAA".test Traceback (most recent call last): 2: from /home/chorder/.rvm/rubies/ruby-2.5.1/bin/irb:11:in <main> 1: from (irb):8 NoMethodError (private method test called for "AAA":String) 2.5.1 :009 > module Test 2.5.1 :010?> using Refine 2.5.1 :011?> "AAA".test 2.5.1 :012?> end => "AAATEST" 2.5.1 :013 >
|
在一个模块中使用refine关键字定义细化之后,在另一个模块中使用using关键字引入这个用于定义细化的模块,细化定义的作用于就只限于调用细化模块的模块内部。
命名空间(Name Space)
在Ruby中,使用::标识引入不同作用域中的类和模块,用于界定类和模块,防止命名冲突,也让源代码的结构更加清晰。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 2.5.1 :001 > module A 2.5.1 :002?> module B 2.5.1 :003?> class C 2.5.1 :004?> D="Constant" 2.5.1 :005?> end 2.5.1 :006?> end 2.5.1 :007?> end => "Constant" 2.5.1 :008 > 2.5.1 :009 > A::B::C::D => "Constant" 2.5.1 :010 >
|