最近睡前把《Ruby元编程》作为枕边书,复习一些元编程技巧。顺手整理记录下这些技巧的同时,也为了这门语言能更广泛地传播,希望有更多的人喜欢Ruby这个神器。
一切都是对象
来到Ruby的世界,请你首先不要被这一切搞晕,实际上这些反而让Ruby的对象模型概念变得更加清晰。一切都是对象,对象是一个类,类也是一个对象。而实际上类是一个带有特殊功能的模块。
| 12
 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诞生自亚洲,一直觉得它是一门充满东方哲学的编程语言。
| 12
 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,可以在任何地方打开这个类,对它添加一些方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | 2.5.1 :001 > class String2.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,让你的方法在下文中得到全局支持:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | 2.5.1 :001 > testTraceback (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一不小心可能会引发一些大的问题。因此有时候需要配合类的细化功能一起使用。
细化的用法如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | 2.5.1 :001 > module Refine2.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中,使用::标识引入不同作用域中的类和模块,用于界定类和模块,防止命名冲突,也让源代码的结构更加清晰。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | 2.5.1 :001 > module A2.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 >
 
 
 |