用实际代码演示Ruby的容易被误解的6个特性
简介:假设您是一名C++开发人员,您需要使用Ruby快速执行一些原型设计。当您拿起一本Ruby参考书籍(比如Pickaxe)或浏览Ruby网站时,会看到一些熟悉的构造,比如类声明、线程支持和异常处理。正当您认为自己了解Ruby的工作原理之时,您意识到了,您Ruby代码中的并发机制与Boost线程工作原理不一样,catch和throw也与它们看上去的大不相同,而且其他人在其Ruby脚本中各处使用了名为self的关键词。欢迎来到Ruby的世界中!
如果您是一名C++程序员且需要在Ruby环境中工作,那么您有一些功课要做。本文讨论了Ruby新手可能会误解的六个Ruby特性,特别是当他或她来自一个类似但又不太相同的环境,比如C++:
●Ruby类层次结构
●Ruby中的单例方法
●self关键词
●method_missing方法
●异常处理
●线程
注意:本文中所有的代码均进行测试,且基于Ruby版本1.8.7。
Ruby中的类层次结构
Ruby中的类层次结构会很棘手。创建一个Cat类型的类并开始探讨其层次结构(参见清单1)。
清单1.Ruby中的隐式类层次结构
irb(main):092:0>classCat irb(main):093:1>end =>nil irb(main):087:0>c=Cat.new =>#<Cat:0x2bacb68> irb(main):088:0>c.class =>Cat irb(main):089:0>c.class.superclass =>Object irb(main):090:0>c.class.superclass.superclass =>nil irb(main):091:0>c.class.superclass.superclass.superclass NoMethodError:undefinedmethod`superclass'fornil:NilClass from(irb):91 from:0
Ruby中的所有对象(甚至用户定义的对象)都是Object类的后代,这在清单1中清晰可见。这与C++是鲜明的对比。这一点也不像普通数据类型,例如C/C++int或double。清单2显示了整数1的类层次结构。
清单2.整数1的类层次结构
irb(main):100:0>1.class =>Fixnum irb(main):101:0>1.class.superclass =>Integer irb(main):102:0>1.class.superclass.superclass =>Numeric irb(main):103:0>1.class.superclass.superclass.superclass =>Object
到目前为止一切顺利。现在您知道了类本身是Class类型的对象。而Class最终派生自Object,如清单3中所示使用Ruby内置的String类。
清单3.类的类层次结构
irb(main):100:0>String.class =>Class irb(main):101:0>String.class.superclass =>Module irb(main):102:0>String.class.superclass.superclass =>Object
Module是Class的基类,但是使用它时有一点要注意,即您不能直接实例化用户定义的Module对象。如果您不想深入Ruby内部,最好考虑与C++命名空间有类似特征的Module:您可以定义您自己的方法、常量、等等。您在Class中包含了一个Module,以及voilà,Module的所有元素现在会魔法般地成为Class的元素。清单4提供了一个示例。
清单4.Module不能进行直接实例化,并且只能与类一同使用
irb(main):020:0>moduleMyModule irb(main):021:1>defhello irb(main):022:2>puts"HelloWorld" irb(main):023:2>end irb(main):024:1>end irb(main):025:0>test=MyModule.new NoMethodError:undefinedmethod`new'forMyModule:Module from(irb):25 irb(main):026:0>classMyClass irb(main):027:1>includeMyModule irb(main):028:1>end =>MyClass irb(main):029:0>test=MyClass.new =>#<MyClass:0x2c18bc8> irb(main):030:0>test.hello HelloWorld =>nil
下面再重申一下重点:当您使用Ruby编写c=Cat.new时,c是派生自Object的Cat类型的一个对象。Cat类是Class类型的一个对象,Class派生自Module,而Module又派生自Object。因此该对象及其类型都是有效的Ruby对象。
单例方法和可编辑类
现在,看一下单例方法。假设您想使用C++建模类似于人类社会的东西。那么您会如何做呢?定义一个名为Human的类,然后定义数百万的Human对象?这更像是在建模一个呆板的社会;每个人必须具惟一的特征。Ruby的单例方法在这里就派上了用场,如清单5所示。
清单5.Ruby中的单例方法
irb(main):113:0>y=Human.new =>#<Human:0x319b6f0> irb(main):114:0>defy.paint irb(main):115:1>puts"Canpaint" irb(main):116:1>end =>nil irb(main):117:0>y.paint Canpaint =>nil irb(main):118:0>z=Human.new =>#<Human:0x3153fc0> irb(main):119:0>z.paint NoMethodError:undefinedmethod`paint'for#<Human:0x3153fc0> from(irb):119
Ruby中的单例方法是仅与特定对象关联的方法,不能用于一般的类。它们的前缀是对象名称。在清单5中,paint方法特定于y对象,而且仅限于y对象;z.paint导致一个“方法未定义”错误。您可以调用singleton_methods来查明一个对象中的单例方法列表:
irb(main):120:0>y.singleton_methods =>["paint"]
不过在Ruby中有另一种定义单例方法的方式。看看清单6中的代码。
清单6.创建单例方法的另一种方式
irb(main):113:0>y=Human.new =>#<Human:0x319b6f0> irb(main):114:0>class<<y irb(main):115:1>defsing irb(main):116:1>puts"Cansing" irb(main):117:1>end irb(main):118:1>end =>nil irb(main):117:0>y.sing Cansing =>nil
清单5还开创了新的可能性,可以添加新方法到用户定义的类和内置的Ruby现有类,比如String。这在C++中是不可能实现的,除非您能够访问您使用的类的源代码。再次观察String类(清单7)。
清单7.Ruby允许您修改一个现有的类
irb(main):035:0>y=String.new("racecar") =>"racecar" irb(main):036:0>y.methods.grep(/palindrome/) =>[] irb(main):037:0>classString irb(main):038:1>defpalindrome? irb(main):039:2>self==self.reverse irb(main):040:2>end irb(main):041:1>end irb(main):050:0>y.palindrome? =>true
清单7清楚地展示了如何编辑一个现有的Ruby类来添加您自行选择的方法。这里,我添加了palindrome?方法到String类。因此Ruby类在运行时是可编辑的(一个强大的属性)。
现在您对Ruby的类层次结构和单例有了一定的认识,接下来我们来看self。注意,在定义palindrome?方法时我使用了self。
发现self
self关键词的最常见用法可能就是在Ruby类中声明一个静态方法,如清单8所示。
清单8.使用self声明类的静态方法
classSelfTest defself.test puts"HelloWorldwithself!" end end classSelfTest2 deftest puts"Thisisnotaclassstaticmethod" end end SelfTest.test SelfTest2.test
从清单8的输出中可以看到(如清单9所示),没有对象您无法调用非静态方法。该行为类似于C++。
清单9.在没有对象的情况下调用非静态方法时会出错
irb(main):087:0>SelfTest.test HelloWorldwithself! =>nil irb(main):088:0>SelfTest2.test NoMethodError:undefinedmethod'test'forSelfTest2:Class from(irb):88
在探讨self更深奥的用途和含义之前,注意您也可以通过在方法名称前面加上类名来在Ruby中定义一个静态方法:
classTestMe defTestMe.test puts"Yetanotherstaticmemberfunction" end end TestMe.test#worksfine
清单10提供了self的一个更有趣但不太容易找到的用法。
清单10.使用元类来声明静态方法
classMyTest class<<self deftest puts"Thisisaclassstaticmethod" end end end MyTest.test#worksfine
该段代码以一种稍微不同的方式将test定义为一个类静态方法。要了解究竟发生了什么,您需要看一下class<<self语法的一些细节。class<<self…end创建一个元类。在方法查找链中,在访问对象的基类之前先搜索该对象的元类。如果您在元类中定义一个方法,可以在类上调用该方法。这类似于C++中静态方法的概念。
可以访问一个元类吗?是的:只需从class<<self…end内返回self。注意,在一个Ruby类声明中,您没有义务仅给出方法定义。清单11显示了元类。
清单11.理解元类
irb(main):198:0>classMyTest irb(main):199:1>end =>nil irb(main):200:0>y=MyTest.new =>#<MyTest:0x2d43fe0> irb(main):201:0>z=classMyTest irb(main):202:1>class<<self irb(main):203:2>self irb(main):204:2>end irb(main):205:1>end =>#<Class:MyTest> irb(main):206:0>z.class =>Class irb(main):207:0>y.class =>MyTest
回到清单7的代码,您会看到palindrome被定义为self==self.reverse。在该上下文中,self与C++没有什么区别。C++和Ruby中的方法都需要一个操作对象,以修改或提取状态信息。self是指这里的这个对象。注意,可以通过附加self前缀来选择性地调用公共方法,指明方法付诸作用的对象,如清单12所示。
清单12.使用self调用方法
irb(main):094:0>classSelfTest3 irb(main):095:1>deffoo irb(main):096:2>self.bar() irb(main):097:2>end irb(main):098:1>defbar irb(main):099:2>puts"TestingSelf" irb(main):100:2>end irb(main):101:1>end =>nil irb(main):102:0>test=SelfTest3.new =>#<SelfTest3:0x2d15750> irb(main):103:0>test.foo TestingSelf =>nil
在Ruby中您无法通过附加self关键词前缀来调用私有方法。对于一名C++开发人员,这可能会有点混淆。清单13中的代码明确表示,self不能用于私有方法:对私有方法的调用只能针对隐式对象。
清单13.self不能用于私有方法调用
irb(main):110:0>classSelfTest4 irb(main):111:1>defmethod1 irb(main):112:2>self.method2 irb(main):113:2>end irb(main):114:1>defmethod3 irb(main):115:2>method2 irb(main):116:2>end irb(main):117:1>private irb(main):118:1>defmethod2 irb(main):119:2>puts"Insideprivatemethod" irb(main):120:2>end irb(main):121:1>end =>nil irb(main):122:0>y=SelfTest4.new =>#<SelfTest4:0x2c13d80> irb(main):123:0>y.method1 NoMethodError:privatemethod`method2'calledfor#<SelfTest4:0x2c13d80> from(irb):112:in`method1' irb(main):124:0>y.method3 Insideprivatemethod =>nil
由于Ruby中的一切都是对象,当在irb提示符上调用self时您会得到以下结果:
irb(main):104:0>self =>main irb(main):105:0>self.class =>Object
一启动irb,Ruby解释器就为您创建主对象。这一主对象在Ruby相关的文章中也被称为顶层上下文。
关于self就介绍这么多了。下面我们接着来看动态方法和method_missing方法。
method_missing揭秘
看一下清单14中的Ruby代码。
清单14.运行中的method_missing
irb(main):135:0>classTest irb(main):136:1>defmethod_missing(method,*args) irb(main):137:2>puts"Method:#{method}Args:(#{args.join(',')})" irb(main):138:2>end irb(main):139:1>end =>nil irb(main):140:0>t=Test.new =>#<Test:0x2c7b850> irb(main):141:0>t.f(23) Method:fArgs:(23) =>nil显然,如果voodoo是您喜欢的,那么清单14会给您这个恩典。这里发生什么了呢?我们创建了一个Test类型的对象,然后调用了t.f,以23作为参数。但是Test没有以f作为方法,您应当会得到一个NoMethodError或类似的错误消息。Ruby在这里做了一件很棒的事情:您的方法调用被阻截并由method_missing处理。method_missing的第一个参数是缺失的方法名,在本例中是f。第二个(也是最后一个)参数是*args,该参数捕获传递给f的参数。您可以在何处使用像这样的参数呢?在众多选项之中,您可以轻松地将方法调用转发到一个包含的Module或一个组件对象,而不为顶级类中的每个调用显式提供一个包装应用程序编程接口。
在清单15中查看更多voodoo。
清单15.使用send方法将参数传递给一个例程
irb(main):142:0>classTest irb(main):143:1>defmethod1(s,y) irb(main):144:2>puts"S:#{s}Y:#{y}" irb(main):145:2>end irb(main):146:1>end =>nil irb(main):147:0>t=Test.new irb(main):148:0>t.send(:method1,23,12) S:23Y:12 =>nil在清单15中,classTest有一个名为method1的方法被定义。但是,这里没有直接调用方法,而是发出对send方法的调用。send是Object类的一个公共方法,因此可用于Test(记住,所有类都派生自Object)。send方法的第一个参数是表示方法名称的一个符号和字符串。send方法可以做到哪些您通常无法做到的事情?您可以使用send方法访问一个类的私有方法。当然,对于这是否是一个好特性仍然颇具争议。看一下清单16中的代码。
清单16.访问类私有方法
irb(main):258:0>classSendTest irb(main):259:1>private irb(main):260:1>defhello irb(main):261:2>puts"SayingHelloprivately" irb(main):262:2>end irb(main):263:1>end =>nil irb(main):264:0>y=SendTest.new =>#<SendTest:0x2cc52c0> irb(main):265:0>y.hello NoMethodError:privatemethod`hello'calledfor#<SendTest:0x2cc52c0> from(irb):265 irb(main):266:0>y.send(:hello) SayingHelloprivately =>nilThrow和catch并非表面那样
如果您像我一样具有C++工作背景,且试图编写异常安全代码,那么在看到Ruby有throw和catch关键词时会开始感到异常亲切。遗憾的是,throw和catch在Ruby中的含义完全不同。
Ruby通常使用begin…rescue块处理异常。清单17提供了一个示例。
清单17.Ruby中的异常处理
begin f=File.open("ruby.txt") #..continuefileprocessing rescueex=>Exception #..handleerrors,ifany ensure f.closeunlessf.nil? #alwaysexecutethecodeinensureblock end在清单17中,如果在试图打开文件时出错(可能是缺少文件或文件权限方面的问题),rescue块中的代码会运行。ensure块中的代码始终运行,不管是否有任何异常引发。注意,rescue块后面是否紧跟ensure块是可选的。另外,如果必须显式地抛出一个异常,那么语法是raise<MyException>。如果您选择拥有您自己的异常类,可能会希望从Ruby内置的Exception类派生出相同的类,以利用现有方法。
Ruby中的catch和throw代码块实际上不是异常处理:您可以使用throw修改程序流程。清单18显示了一个使用throw和catch的示例。
清单18.Ruby中的Throw和catch
irb(main):185:0>catch:labeldo irb(main):186:1*puts"Thiswillprint" irb(main):187:1>throw:label irb(main):188:1>puts"Thiswillnotprint" irb(main):189:1>end Thiswillprint =>nil在清单18中,当代码运行到throw语句时,执行会被中断,解释器开始寻找处理相应符号的一个catch块。在catch块结束的地方继续执行。查看清单19中的throw和catch示例:注意,您可以轻松将catch和throw语句用于各个函数。
有些人甚至说,Ruby中对catch和throw的支持将Cgoto行为带到一个全新的高度。鉴于函数可以有多个嵌套层,而catch块可能在每一级,goto行为类比似乎有据可循。
清单19.Ruby中的异常处理:嵌套的catch块
irb(main):190:0>catch:labeldo irb(main):191:1*catch:label1do irb(main):192:2*puts"Thiswillprint" irb(main):193:2>throw:label irb(main):194:2>puts"Thiswon'tprint" irb(main):195:2>end irb(main):196:1>puts"Neitherwillthisprint" irb(main):197:1>end Thiswillprint =>nilRuby中的线程可以是绿色的
Ruby版本1.8.7不支持真正的并发性。确实不支持。但是您会说,在Ruby中有Thread构造函数。您说的没错。不过这个Thread.new不会在您每次调用同一方法时生成一个真实的操作系统线程。Ruby支持的是绿色线程:Ruby解释器使用单一操作系统线程来处理来自多个应用程序级线程的工作负载。
当某个线程等待一些输入/输出发生时,这一“绿色线程”概念很有用,而且您可以轻松调度一个不同的Ruby线程来充分利用CPU。但是这一构造函数无法使用现代的多核CPU(维基百科提供了一段内容,很好地解释了什么是绿色线程。参见参考资料获取链接)。
最后这一个示例(参见清单20)证明了这一点。
清单20.Ruby中的多个线程
#!/usr/bin/envruby deffunc(id,count) i=0; while(i<count) puts"Thread#{i}Time:#{Time.now}" sleep(1) i=i+1 end end puts"Startedat#{Time.now}" thread1=Thread.new{func(1,100)} thread2=Thread.new{func(2,100)} thread3=Thread.new{func(3,100)} thread4=Thread.new{func(4,100)} thread1.join thread2.join thread3.join thread4.join puts"Endingat#{Time.now}"
假设您的Linux?或UNIX?机器上拥有top实用程序,在终端运行代码,获取进程ID,然后再运行top–p<processid>。top启动后,按住Shift-H来列出运行中线程的数量。您应当只能看到一个线程,确认了这一点:Ruby1.8.7中的并发性不过是个神话。
总的看来,绿色线程没有什么坏处。它们在重负荷输入/输出密集型程序中仍然有用,更不用说该方法可能是操作系统间最可移植的一个了。
结束语
本文涵盖了以下多个方面:
●Ruby中类层次结构的概念
●单例方法
●解释self关键词和method_missing方法
●异常
●线程
尽管Ruby不乏特立独行之处,但是使用它进行编程还是挺有趣的,而且其以最少的代码完成大量工作的能力还是很强大的。难怪Twitter这样的大型应用程序会使用Ruby来驾驭其真正的潜力。祝您有个快乐的Ruby编程体验!