Programming Ruby

实用程序员指南

上一章 < 目录^
下一章>

深入方法


一些其它语言有函数,过程,方法等,而Ruby中只有方法:一段表达式代码,返回一个值。

到目前为止,我们在这本书中只是基本的介绍了如何定义,使用方法,现在,我们会继续深入的探讨一些关于方法更深层的东西。

方法定义

如同在前面看到的一样,定义一个方法用关键字def开头,方法名应该以小写字母开头[如果你用大写字母开头定义一个方法,你不会立即得到一个错误,但是当你调用这个方法时,Ruby首先认为你访问的是一个常量,所以可能会解析错误],如果一个方法主要用来完成一些查询操作(不专指数据库查询),通常以一个问号"?"结束,作为函数名的最后一个字母,比如instance_of?等。如果一个方法有一定危险,或者可能修改方法的接受者,通常以"!"结尾,比如String类提供了chop和chop!两个方法,第一个方法返回一个修改过的字符串,而第二个方法直接就修改了接收者本身。"?"和"!"是唯一两个能作为方法名后缀的特殊字符。

我们已经指定了方法名,如果需要,我们可以定义一些参数,这些参数用双括号括起来,作用域范围都是局部变量,一些例子如下:

def myNewMethod(arg1, arg2, arg3)     # 3 arguments
  # Code for the method would go here
end

def myOtherNewMethod                  # No arguments    
  # Code for the method would go here    
end
  

Ruby允许为方法的参数设置默认值:如果调用者没有显示的为这些参数提供值,将使用这些默认值。通过"=",就可以为这些参数设定默认值。

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
  "#{arg1}, #{arg2}, #{arg3}."
end
coolDude ? "Miles, Coltrane, Roach."
coolDude("Bart") ? "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood") ? "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus") ? "Bart, Elwood, Linus."

方法体中包含了一般的Ruby表达式,但是你不能在方法里面定义实例方法,类或者模块。方法的返回值是方法体最后一行执行后的结果,或者你显示的用一个return语句。

可变长度的参数列表

如果我们想给方法传入一个数目不定的参数,或者把所有参数放到一个参数中进行传递的话,该怎么办呢?我们可以在普通的参数后面加入一个特殊的参数,这个参数以"*"开头,就可以达到这个目的了。

def varargs(arg1, *rest)
  "Got #{arg1} and #{rest.join(', ')}"
end
varargs("one") ? "Got one and "
varargs("one", "two") ? "Got one and two"
varargs "one", "two", "three" ? "Got one and two, three"

在这个例子中,第一个参数很普通,直接作为第一个参数变量,而后面以"*"开头的参数,将会包括调用时候后面的所有参数,是一个Array的结构,包括了从第二个开始的所有参数。

方法和块

在讨论块和迭代的那章时,我们知道,当一个方法被调用时候,可以接收一个block,而我们在方法中可以用yield来执行这个block。

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end

takeBlock("no block") ? "no block"
takeBlock("no block") { |s| s.sub(/no /, '') } ? "block"

但是,当方法接受参数中最后一个参数以"&"开始的时候,任何给定的block都会转换为Proc对象,并且这个Proc对象将会赋值给这个参数(下例中block指向一个Proc对象)。

class TaxCalculator
  def initialize(name, &block)
    @name, @block = name, block
  end
  def getTax(amount)
    "#@name on #{amount} = #{ @block.call(amount) }"
  end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100) ? "Sales tax on 100 = 7.5"
tc.getTax(250) ? "Sales tax on 250 = 18.75"

调用方法

通常,调用一个方法需要指定一个接收者,方法名,还有一些参数或者block。

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

在这个例子里,connection是接收者,downloadMP3是方法名,"jitterbug"是一个参数,{ |p| showProgress(p) }是传递给这个方法的块。

对于类或者模块方法来说,接收者是类或模块名:

File.size("testfile")
Math.sin(Math::PI/4)

如果你省略了接收者,那么默认为self是接收者,即当前对象:

self.id ? 537794160
id ? 537794160
self.type ? Object
type ? Object

这种机制也是Ruby实现private方法的体现,private方法不能用一个接收者来直接调用,只能在当前对象中使用。

 方法名后面是可选的参数,如果不会出现歧义的话,调用方法时参数可以不加括号括起来[Ruby文档有时候也叫做这样的方法是命令(commands)],然而,除非特别简单的方法,否则还是加上括号的好,要不可能容易出错,比如,你的方法嵌套在另一个方法调用之中。

a = obj.hash    # Same as
a = obj.hash()  # this.



obj.someMethod "Arg1", arg2, arg3   # Same thing as     
obj.someMethod("Arg1", arg2, arg3)  # with parentheses.

在方法调用时使用数组

前面我们已经说过了,在一个方法的参数前面可以加一个星号,这样所有后面的参数都被放到了一个数组中,反过来,Ruby也支持调用的时候指定一个数组代替若干个参数。

在调用方法的时候,你可以使用一个数组作为一个参数,它的每个元素都将作为一个单独的参数使用。使用的时候,需要在这个作为参数的数组前面加一个星号。

def five(a, b, c, d, e)
  "I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 ) ? "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b']) ? "I was passed 1 2 3 a b"
five(*(10..14).to_a) ? "I was passed 10 11 12 13 14"

更加动态的block

我们已经看过了如何把一个方法和一个块联系起来。

listBones("aardvark") do |aBone|
  # ...
end

通常,这已经足够好了,我们可以给一个方法提供一个机构良好的块,而不必再方法中使用很多的if或者while等语句。

 但是有些时候,你需要更灵活一些,比如,下面的例子,如果选择times,即输入t,将会打印2,4,6,8等等:

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i



if times =~ /^t/      
  puts((1..10).collect { |n| n*number }.join(", "))      
else      
  puts((1..10).collect { |n| n+number }.join(", "))      
end
结果:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

虽然这样可以工作,但是不是很完美,我们可以把负责计算的部分抽出来组成一个block。

 

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i


if times =~ /^t/      
  calc = proc { |n| n*number }      
else      
  calc = proc { |n| n+number }      
end      
puts((1..10).collect(&calc).join(", "))
produces:
(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

如果最后一个方法的最后一个参数以"&"开头,Ruby把它最为一个Proc来处理,传到相应的block。

这种技术也有另外的用处,比如我们使用迭代器处理一些数据,把每个步骤地结果存储到一个数组中,我们下面将用到前面的Fibonacci 例子来产生一组数据:

a = []
fibUpTo(20) { |val| a << val } ? nil
a.inspect ? "[1, 1, 2, 3, 5, 8, 13]"

尽管这样已经可以工作了,但是这显示出来的意图不像我们想象的那么明晰,所以,我们取而代之的是另外定义了一个方法into,它将返回一个完成填充array功能的block。(注意返回的block是一个闭包closure ,即使into返回了,它还指向参数anArray

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect ? "[1, 1, 2, 3, 5, 8, 13]"

哈希结构作为参数

一些语言支持基于键的参数,即hash结构的参数。不按照参数的个数和位置来调用一个方法,而是用一个hash结构的键-值结构来设定参数,而不是按位置。Ruby1。6不支持这种特性,1。8支持。[本书写的是基于1.6,而目前最新的Ruby是1.8]

同时,人们可以用hash结构来实现这一功能,比如,我们要为我们的SongList实现一个按名字查找的功能。

 

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   } )

第一个参数是查找的名称,第二个参数是一个hash结构,包含了各种查找的参数。使用hash结构,我们可以是有一些键-值特性:音乐流派是jazz,时长小于4.5分钟。但是这段代码不是太好,而且大括号中的内容很容易被误认为块。所以,Ruby提供了一个快捷方式,你可以在方法的参数中指定键=>值的结构,像普通的参数那样。这样的结构都将作为一个hash结构传给方法,而不需要大扩号了。

aList.createSearch("short jazz songs",
                   'genre'            => "jazz",
                   'durationLessThan' => 270
                   )


上一章 < 目录 ^
下一章 >

Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Copyright © 2001 by Addison Wesley Longman, Inc. This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/)).

Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder.

Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.