Day10 读书笔记&心得体会

一、心得体会
1、今天完成了什么?

  • 今天看了20页的镐头书(139-160)
  • 看了10个controller

2、今天收获了什么?

  • Ruby中的循环有哪些?
  • 捕获、异常和抛出是怎么工作的?
    • 含有异常信息的数据包(package)是Exception类或其子类的一个对象。
    • Ruby将相关的Exception对象的引用放在全局变量$!中,这与任何随后的异常处理不相干。不带任何参数的调用raise,它会重新引发$!中的异常。举个栗子:
op_file = File.open(opfile_name, "w")
begin
# 这段代码引发异常会被下面的rescue语句捕获
while data = socket.read(512)
  op_file.write(data)
end

rescue SystemCallError
  $stderr.print "IO failed: " + $!
  op_file.close
  File.delete(opfile_name)
  raise
end
  • 10个controller:取送、取送日志、取送跟进、取送跟进、疑难原因、区县(districts)、礼物(gifts)、催单(hastens)、盘点(inventories)、盘点订单

3、今天犯了哪些错误?
4、明天需要做哪些工作?

二、读书笔记
昨天我学到的最重要的是什么?
Ruby有几种循环:

  • while
  • until
  • 迭代器循环,如times、upto(递增)、downto(递减)、each、step
  • For..in

7.6.3 Break、Redo和Next

循环控制结构break,redo和next可以让你改变循环或者迭代的正常流程。

  • break终止最接近的封闭循环体,然后继续执行block后面的语句。
  • redo从循环头重新执行循环,但不重计算循环条件表达式或者获得迭代中的下一个元素。
  • next跳到本次循环的末尾,并开始下一次迭代
while line = gets
  next if line =~ /^\s*#/ # skip comments
  break if line =~/^END/ # stop at end
  
  redo if line.gsub!(/'(.*?)'/) { eval($1) }
  # process line ...
end

这些关键字还可以和任何基于迭代的循环机制一起使用。

i = 0
loop do
  i += 1
  next if i <3
  print i
  break if i > 4
end

输出结果:

345

7.6.4 Retry
redo语句使得一个循环重新执行当前的迭代,但是有时你需要从头重新执行一个循环,retry语句就是做这件事的,它重新执行任意类型的迭代式循环。

for i in 1..100
  print "Now at #{i}. Restart? "
  retry if gets =~ /^y/i
end

交互式地运行这段代码,你会看到:
Now at 1. Restart? n
Now at 2. Restart? y
Now at 1. Restart? n

retry在重新执行之前会重新计算传递给迭代的所有参数。下面是一个DIY的until循环的例子。

def do_until(cond)
  break if cond
  yield
  retry
end
i = 0
do_until(i>10) do
  print i, " "
  i += 1
end

7.7 变量作用域、循环和Blocks

while、until和for循环内建到了RUby语言中,但没有引入新的作用域,前面已经存在的局部变量可以在循环中使用,而循环中新创建的局部变量也可以在循环后使用。

被迭代器使用的block(比如loop和each)与此略有不同,通常,在这些block中创建的局部变量无法在block外访问。

[1, 2, 3 ].each do |x|
 y = x + 1
end
[x, y]

输出结果:

NameError: undefined local variable or method `y' for main:Object
   from (irb#1):126

然而,如果执行block的时候,一个局部变量已经存在且与block中的变量同名,那么block将使用此已有的局部变量,因而,它的值在块后面仍然可以使用。

如下面的例子所示,此即适用于block中的普通变量也适用于block的参数。

x = nil
y = nil
[1, 2, 3].each do |x|
y = x + 1
end
[x, y] -> [3, 4]

为什么终端结果是这样的:

[nil, 4]

注意在外部作用域中变量不必有值:Ruby解释器只需要看到它即可。

if false
  a = 1
end
3.times {|i| a = i}

第8章
异常、捕获和抛出(Exceptions,Catch and Throw)

到目前为止,我们都是在欢乐谷中开发代码,每个程序库的调用都是成功的,用户从来没有输入不准确的数据。但是,在现实世界中,错误会发生,好的程序可以预见它们的发生,然后优雅地处理这些错误,但这并非总像听起来那么简单,通常检测到错误出现的那部分代码,缺少有关如何处理它的上下文信息。

举个栗子:

试图去打开一个并不存在的文件,在一些情况下是可行的的,但在别的情况下却是致命的错误,文件处理模块要如何做呢?

传统的做法是使用返回码。open方法在失败时会返回一些特定值,然后这个值会沿着调用例程的层次往回传播,直到有函数想要处理它。

这种做法问题是,管理所有这些错误码是一件痛苦的事情。如果函数首先调用open,然后调用read,最后调用close方法,而且每个方法都有可能返回错误标识,那么当函数将返回码返回给调用者时,该如何区分这些错误码呢?

异常在很大程度上解决了这个问题,异常允许把错误信息打包到一个对象中,然后该异常对象被自动传播调用栈(calling stack),直到运行系统找到明确声明直到如何处理这类异常的代码为止。

8.1 异常类(The Ecxeption Class)

含有异常信息的数据包(package)是Exception类、或其子类的一个对象。Ruby预定义了一个简洁的异常层次结构,这个层次结构使得处理异常变得相当简单。

当需要引发(raise)异常时, 可以使用某个内建的Exception类,或者创建自己异常类。如果创建自己的异常类,可能你希望它从StandardError类或其子类派生,否则,你的异常在默认情况下不会被捕获。

每个Exception都关联有一个消息字符串和栈回溯信息(backtrace)。如果定义自己的异常,可以添加额外的信息。

8.2 处理异常(Handing Exception)

我们的点唱机用TCP套接字从互联网下载歌曲。它的基本代码很简单(假设文件名个套接字都已经创建好)。

op_file = File.open(opfile_name, "w")
while data = socket.read(512)
  op_file.write(data)
end

如果下载过程中得到一个致命错误,会发生什么呢?我们肯定不想在歌曲列表中存储一首不完整的歌曲。“I Did it My...”

让我们添加一些处理异常的代码,看看它是如何帮助处理异常的。在一个beigin/end块中,使用一个或多个rescue语句告诉Ruby希望处理的异常类型。

在这个特定的例子中,我们感兴趣的是捕获SystemCallError异常(同时暗含着任何SystemCallError子类的异常),所以它就是出现在resuce行的异常类型,在这个错误处理block中,我们报告了错误,关闭和删除了输出文件,同时重新引起异常。

op_file = File.open(opfile_name, "w")
begin
#这段代码引发异常会被
#下面的rescue语句捕获
while data = socket.read(512)
  op_file.write(data)
end

rescue SystemCallError
  $stderr.print "IO failed: " + $!
  op_file.close
  File.delete(opfile_name)
  raise
end

当异常被引发时,Ruby将关于Exception对象的引用放在全局变量$!中,这与任何随后的异常处理不相干。

Exception

  • fatal(used internally by Ruby)

  • NoMemoryError

  • ScriptError

    • LoadError
    • NotImplementError
    • SyntaxError
  • SignalException

    • Interrupt
  • StandardError

    • ArgumentError
    • IOError
      • EOFError
    • IndexError
    • LocalJumpError
    • NameError
      • NoMethodError
    • RangeError
      • FloatDomainError
    • RegexpError
    • RuntimeError
    • SecurityError
    • SystemCallError
      • system-dependent exception(Errno::XXX)
    • ThreadError
    • TypeError
    • ZeroDivisionError
  • SystemExit

  • SystemStackError

这个感叹号大概反映出了我们的惊讶,我们的代码竟然会导致错误!在前面的例子中,我们用$!变量去格式化错误信息。
关闭和删除文件后,我们可以不带任何参数来调用raise,它会重新引导$!中的异常,这是一个有用的技术,它允许我们先编写代码过滤掉一些异常,再把不能处理的异常传递到更高的层次。这几乎就像实现了一个错误处理的继承层次结构。

在begin块中可以有多个rescue子句(clause),每个rescue子句可以指示捕获多个异常,在rescue子句的结束处,你可以提供一个Ruby的局部变量名来接收匹配的异常,许多人发现这比导出使用$!有更好的可读性。

begin
  eval string
 rescue SyntaxError, NameError => boom
  print "String doesn't compileL: " + boom
 rescue Standard => bang
  print "Error running script: " + bang
end

RUby如何决定执行哪个rescue子句呢?

这个处理非常类似于对case语句的处理,RUby用引发的异常依次比较begin块中每个rescue子句的每个参数。如果引发的异常匹配了一个参数,Ruby就执行rescue的程序体,同时停止比较。

匹配是用parameter ===$!完成的。对于大多数异常来说,如果rescue子句给出的类型,与当前引发的异常的类型相同,或者它是引发异常的超类(superclass),这意味着匹配是成功的。如果编写一个不带参数表的rescue子句,它的默认参数是StandardError。

注意:可以这样进行比较的原因是:异常是类,而类进而是某种Module。===方法是为模块定义的,如果操作数的类与接收者相同或者接收者的祖先,这个方法返回true。

如果没有任何rescue子句与之匹配,或者异常在begin/end块外面被引发,Ruby就沿着调用栈向上查找,在调用者上寻找异常的处理者,接着在调用者的调用者上寻找,依次类推。

尽管,rescue子句的参数通常是Exception类的名称,实际上它们可以是任何返回的Exception类的表达式(包括方法调用)。

8.2.1 系统错误(System Errors)

当对操作系统的调用返回错误码时,会引发系统错误,在POSIX系统上,这些错误名称有诸如EAGAIN和EPERM等(在Unix机器上,键入man error,你会得到这些错误的列表)。

Ruby得到这些错误,把每个错误装(wrap)到特定的对象中,每个错误都是SystemCallError的子类,定义在Errno模块中,这意味着,你会发现类名如Errno::EAGIN, ERRno::EIO和Errno::EPERM等的异常,如果想得到底层的系统错误码,则把每个Errno异常对象有一个Errno(有点令人疑惑)的类常量(class constant),它包含相应的系统错误。

Errno::EAGAIN::Errno -> 35
Errno::EPERM::Errno -> 1

注意到EWOULDBLOCK和EAGAIN有相同的错误码,这是我电脑上的操作系统的一个特性——两个常量映射到相同的错误码。为了处理这种情况,Ruby做出了安排,让Errno::EAGAIN和Errno::EWOULDBOCK在rescue子句中被等同对待,如果你要求rescue其中一个错误,那么另一个也会被rescue。通过定义SystemCallError#===可以做到这点,因此,如果要比较SystemCallError的两个子类,是比较它们的错误码而不是它们在层次结构中的位置。

8..2.2 善后(Tidying Up)
有时候你需要宝恒一些处理在block结束时能够被执行,而不管是否有异常引发。比如,也许在block的入口处(entry)打开一个文件,需要确保当block退出时它会被关闭。

ensure子句就是做这个的。ensure跟在最后的rescue子句后面,它包含一段block退出时总是被执行的代码。不管block是否正常退出,是否引发并rescue异常,或者是否被捕获的异常终止——这个ensure块总会得到运行。

f = File.open("testfile")
begin
# .. process
rescue
# .. handle error
ensure
  f.close unless f.nil?
end

尽管不是那么有用,else子句是一个类似ensure子句的构造,如果存在的话,它会出现在rescue子句之后和任何一个ensure子句之前。else子句的程序体,只有当主代码体没有引发任何异常时才会被执行。

f = File.open("testfile")
begin
# ..
rescue
# ..
else
# ..
puts "congratulations -- no errors!"
ensure
  f.close unless f.nil
end

8.2.3 再次执行(Play It Again)
有时候也许可以纠正异常的原因。在这些例子中,你可以在rescue子句中使用retry语句去重复执行整个begin/end区块。显然这很可能导致无线循环,所以使用这个特性应该倍加小心(同时把一根手指轻轻放在键盘的中断键上,随时准备着)。

下面的例子再出现异常时会重新执行,它来自Minero Aoki的net/stmp.rb。

@esmtp = true
begin
#首先尝试扩展登录,如果因为服务器不支持而失败
#则使用正常登陆
  if @esmtp then
    @command.ehlo(helodom)
  else
    @command.helo(helodom)
  end

rescue ProtocolError
  if @esmtp then
    @esmtp = false
    retry  
else
    raise
  end
end

这段代码首先使用EHLO命令试图连接SMTP服务器,而这个命令并没有被广泛支持,如果连接尝试失败了,则设置@esmtp变量为false,同时重试连接。如果第二次连接也失败了,则引发异常给它的调用者。

8.3 引发异常(Raising Exceptions)
到目前为止,我们一直都处于守势,处理那些被别人引发的异常,该是轮到我们进攻的时候了(有些人说本书这些温和的作者总是咄咄逼人,但那是另外一本书)。

可是使用Kernel.raise方法在代码中引发异常(或者它有点判决意味的同义词,Kernel.fail)

raise
raise "bad mp3 encoding"
raise InterfaceException, "Keyboard failure", caller

第一种形式只是简单地重新引发当前异常(如果没有当前异常的话,已发RuntimeError)。这种形式用于首先截获异常再将其继续传递的异常处理方法中。

第二种形式创建新的RuntimeError异常,把它的消息设置为指定的字符串,然后异常随着调用栈向上引发。

第三种形式使用第一个参数创建异常,然后把相关联的消息设置给第二个参数,同时把栈信息(trace)设置给第三个参数。通常,第一个参数是Exception层次结构中某个类的名称,或者是某个异常类的对象实例的引用,通常使用Kernel.caller方法产生栈信息。
下面是使用raise的典型例子。

raise
raise "Missing name" if name.nil?
if i >= names.size
  raise IndexError, "#{i} >= size (#{names.size})"
end
raise ArgumentError, "Name too big", caller

最后这个例子从栈回溯信息删除当前函数,这在程序库模块中十分有用。可以更进一步:下面的代码通过只将调用栈的子集传递给新异常,从而达到从栈回溯信息中删除两个函数的目的。

raise ArgumentError, "name too big", caller[1..-1]

8.3.1 添加信息到异常(Adding Information to Exceptions)
你可以定义自己的异常,保存任何需要从错误发生地传递出去的信息。

例如,取决于外部环境,某种类型的网络错误可能是暂时的。如果这种错误发生了,而环境是适宜的,则可以在异常中设置一个标志,告诉异常处理程序重试这个操作可能是值得。

class RetryExceptipn < RuntimeError
  attr :ok_to_retry
  def initialize(ok_to_retry)
    @ok_to_retry = ok_to_retry
  end
 end

注意:从技术层面讲,这个参数可以是任何对象,只要它能响应消息Exception,且这个消息返回一个能够满足object.kind_of?(Exception)为真的对象。

在下面的代码里面,发生了一个暂时的错误。

def read_data(socket)
  data = socket.read(512)
  if data.nil?
    raise RetryException.new(true), "transient read error"
  end
  # .. 正常处理
end

在上一级的调用栈处理了异常。

begin
  stuff = read_data(socket)
  # .. process stuff
 resuce RetryException => detail
  retry of detail.ok_to_retry
  raise
end

8.4 捕获和抛出(Catch and Throw)

尽管raise和rescue的异常机制对程序出错时终止执行已经够用了,但是如果在正常处理过程期间能够从一些深度嵌套的结构中跳转出来,则是很棒的,catch和throw应运而生,可以方便地做到这点。

catch (:done) do
  while line = gets
    throw :done unless fields = line.split(/\t/)
    songlist.add(Song.new(*fields))
  end
  songlist.play
end

catch定义了以给定名称(可能符号或字符串)为标签的block,这个block会正常执行直到晕倒throw为止。

当Ruby碰到throw,它迅速回溯(zip back)调用栈,用匹配的符号寻找catch代码块,当发现它之后,Ruby将栈清退(unwind)到这个为止并终止该block。所以,在前面的例子中,如果输入没有包含正确格式化的行,throw会跳到相应的catch代码块的结束处,不仅终止了while循环,而且跳出了歌曲列表的播放。

如果调用throw时制定了可选的第二个参数,这个值会作为catch的值返回。

在下面的例子中,如果响应任意提示符时键入!,使用throw终止与用户的交互。

def prompt_and_get(prompt)
  print prompt
  res = readline.chomp
  throw :quit_requested if res == "!"
  res
end

catch :quit_requested do
  name = prompt_and_get("Name: ")
  age = prompt_and_get("Age:")
  sex = prompt_and_get("Sex:")
end

这个例子说明了throw没必要出现在catch的静态作用域中。

第9章 Modules

模块一种将方法、类与常量组织在一起的方式,模块给你提供了两个主要的好处:
1.模块提供了命名空间(namespace)来防止命令冲突
2.模块实现了mixin功能

9.1 命名空间(Namespace)

当你开始编写越来越大的Ruby程序时,你自然会发现自己编写了许多可重用的代码——将先关的例程(routine)组成一个库通常是合适的。你会希望将这些代码分解不同的文件,使其内容可以被其他不同的Ruby程序共享。

通常代码会被组织为类,你可能会让一个类(或一组相关的类)对应一个文件。

不过,有时你想要把那些无法自然构成类的部分集合到一起。
一种初步的方法是将所有内容放到一个文件中,然后简单地在任何需要它的程序中加载(load)它,这是C语言工作的方式,不过,这种方式有一个问题。假设你要编写一组三角函数sin、cos等等,你将它们全部塞到一个文件trig.rb中,为后世享用。

同时,Sally想要模拟善良和邪恶,并且她编写了一组对自己有用的例程,包括be_good和sin,并将它们放到moral.rb中,Joe想要编写一个程序找出针尖上有多少跳舞的天使,想要在他的程序中加载。

答案是使用模块机制,模块定义了一个namespace(命名空间),它是一个沙箱(sandbox),你的方法和常量可以在其中任意发挥,而无需担心被其他方法或常量干扰,三角函数可以放到一个模块中。

module Trig
  PI = 3.1415926
  def Trig.sin(x)
  end
  def Trig.cos(x)
  end
end

而品行好坏的方法可以放到另一个模块中。

module Moral
  VARY_BAD = 0
  BAD = 1
  def Moral.sin(badness)
  end
end

模块常量的命名和类常量一样,都以大写字母开头,方法定义同样看起来很相似:这些模块方法就类似于类方法的定义。

如果第三方的程序想要使用这些模块,它可以简单地加载两个文件(使用Ruby的require语句)并引用它们的完整名称(qualified name)。

require 'trig'
require 'moral'

y = Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)

同类方法一样,你可以用模块名和句点来调用模块方法, 使用模块名和两个冒号来引用常量。

9.2 Mixin
模块有另一个妙用,它提供了一种称为mixin的功能,以雷霆之势,极大地的消除了对多重继承的需要。

在上一节的示例中,我们定义了模块方法,它们的名字都以模块名为前缀,如果这让你想到类方法,你接下来的想法可能是“如果我在模块内定义实例方法会怎么样?”

好问题,模块并没有实例,因为模块并不是类,不过,你可以在类的定义中,include一个模块,当包含发生时,模块所有的实例方法瞬间在类中也可以使用了。它们被混入(mixin)了,实际上,所混入的模块其实际行为就像是一个超类。

module Debug
  def who_am_i?
    "#{self.class.name} (\##{self.object_id}): #{self.to_s}"
  end
end

class Phonograph
  include Debug
  # ...
end

class EightTrack
  include Debug
  # ..
end

ph = Phonograph.new("West End Blues")
et = EightTrack.new("Surrealistic Pillow")

ph.who_am_i?
et.who_am_i?

通过包含Debug模块,Phonograph和EightTrack都得以访问who_am_i?这个实例方法。

在继续前行之前,我们将探讨关于include语句的几点问题,首先,include与文件无关,C程序员使用叫做#include的预处理器指令,在编译期将一个文件的内容插入到另一个文件中,Ruby语句只是简单地产生一个指向指定模块的引用。如果模块位于另一个文件中,在使用include之前,你必须使用require(或者不那么常用的旁系,load)将文件加载进来。第二点,Ruby的include并非简单地将模块的实例方法拷贝到类中,相反,它建立一个由类到所包含模块的引用。

如果多个类包含这个模块,它们都指向相同的内容,即使当程序正在运行时,如果你改变模块中一个方法的定义,所有包含这个模块的类都会表现出新的行为。

Mixin为你向类中添加功能,提供了一种控制精巧的方式,不过,它们真正的力量的,当mixin的代码和使用它的类中的代码开始交互时,它们会一起迸发出来,让我们以标准的Ruby mixin——Comparable为例。

你可以使用Comparable mixin向类中添加比较操作符(<, <=, ==, >=和>)以及between?方法。为了使其能够工作,Comparable假定任何使用它的类都定义了<=>操作符,这样,作为类的一个编写者,你定义一个方法<=>,再包含Comparable,然后就可以免费得到6个比较函数,让我们用Song类来尝试一下,令它们基于时长来进行比较,我们所要做的就是包含Comparable模块并实现比较操作符<=>。

class Song
  include Comparable
  def initialize(name, artist, duration)
    @name = name
    @artist = artist
    @duration = duration
  end

  def <=>(other)
    self.duration <=> other.duration
  end
end

我们可以用几首测试歌曲来检查一下结果。

song1 = Song.new("My Way", "Sinatra", 225)
song2 = Song.new("Bicyclops", "Fleck", 260)

song1 <=> song2 
song2 < song2
song1 == song1
song1 > song2

9.3 迭代器与可枚举模块(lterators and the Enumerable Module)

你可能已经注意到Ruby收集(collection)类支持大量针对收集的各种操作:遍历、排序等等,你可能会想,“哎呀,如果我自己的类也能支持这些出色的特性,那就太好了!”
当然,你的类可以支持所有这些出色的特性,感谢mixin和Enumerable模块的魔力,你要做的就是编写一个称为each的迭代器,由它依次返回收集中的元素。包含Enumerable,然后你的类瞬间支持诸如map、include?和find_all等操作。

如果在你收集的对象中使用<=>方法是想了有意义的排序语义,你还会得到诸如min、max和sort等方法。

9.4 组合模块(Composing Modules)

我们讨论了Enumerable的inject方法,Enumerable是另一个标准的mixin,它基于宿主类(host class)中的each实现了许多方法,因此,我们可以在任何包括了Enumerable模块并定义了each方法的类中使用了inject。许多内建的类都是如此。

[1, 2, 3, 4, 5 ].inject {|v, n| v+n }
('a'..'m').inject {|v,n| v+n}

我们还可以定义自己的类以包含Enumerable,继而得到inject支持。

class VowelFinder
  include Enumerable
  def initialize(string)
    @string = string
  end
  def each
    @string.scan(/[aeiou]/) do |vowel|
      yield vowel
    end
  end
end

vf = VowelFinder.new("the quick brown fox jumped")
vf.inject {|v,n| v+n}

注意,我们使用了和前面实例中调用inject的相同模式——使用它来求和,当作用于数字时,它返回算术和,当作用于字符串时,返回串联的字符串。我们也可以使用一个模块来封装这个功能。

module Summable
  def sum
    inject {|v, n| v+n}
  end
end

class Array
  include Summable
end

class Range
  include Summable
end

class VomelFinder
  include Summable
end

[1, 2, 3, 4, 5].sum

vf = VowelFinder.new("the quick brown fox jumped")
vf.sum 

9.4.1 Mixin中的实例变量(Instance variables in Mixins)
从C++转向Ruby的人经常问我们,“mixin中的实例变量会如何呢?在C++中,我必须兜好几个圈子才能控制如何在多重继承中共享变量。Ruby是如何处理的呢?”

我们告诉他们,好吧,对初学者来说,这根本不是什么问题,回忆一下Ruby中实例变量是如何工作的:当前缀为@的变量第一次出现时,即在当前对象(也就是self)中创建实例变量。

对mixin来说,这意味着你要混入客户类中的模块,可能会在客户对象中创建实例变量,并可能使用attr_reader或类似方法,定义这些实例变量的访问方法。例如,下面实例中的Observable模块,会向包含它类中添加实例变量@observer_list。

module Observable
  def observers
    @observer_list ||= []
  end
  
  def add_observer(obj)
    observers << obj
  end

  def notify_observers
    observers.each {|o| o.update}
  end
end

多数时候,mixin模块并不带有它们自己的实例数据——它们只是使用访问访问方法从客户对象中取得数据。但是,如果你要创建的mixin不得不持有它们自己的状态。确保这个实例变量具有唯一的名字,可以对系统中其他的mixin区别开来(也许使用模块名作为变量名的一部分)。或者,模块可以使用模块一级的散列表,以当前对象的ID作为索引,来保存特定于实例的数据,而不必使用Ruby的实例变量。

module Test
  State = {}
  def state = (value)
    State[object_id] = value
  end
  def state
    State[object_id]
  end
end

class Client
  include Test
end

c1 = Client.new

9.4.2 解析有歧义的方法名(Resolving Ambiguous Method Names)

关于mixin,人们经常问到的另一个问题是,方法查找是如何处理的?特别的是,如果类、父类以及类所包含的mixin中,都定义有相同名字的方法时,会发生什么?

答案是,Ruby首先会从对象的直属类(immediate class)中查找,然后是类所包含的mixin,之后是超类以及超类的mixin。如果一个类有多个混入的模块,最后一个包含的模块将会被第一个搜索。

9.5 包含其他文件(including other Files)

因为Ruby可以使我们轻松地编写良好的、模块化的代码,你经常会发现自己编写了含有大量自包含功能的小文件——比如x的接口、完成y的算法,等等。典型地,你会将这些文件组织为类或库。

产生这些文件之后,你希望在新的程序中结合使用它们,Ruby有两个语句来完成这一点,每次当load方法执行时,都会将制定的Ruby源文件包含进来。

load "filename.rb"

更常见的是使用require方法来加载指定的文件,且只加载一次。

require 'filename'
被加载文件中的局部变量不会蔓延到加载它们所在的范围中。例如,这里有一个名为include.rb。

a = 1
def b 
 2
end

下面是当我们把它包含到另一个文件中时,将会发生什么?

a = "cat"
b = "dog"
require "included"

require有额外的功能:它可以加载共享的二进制库,两者都可以接受相对或者绝对路径,如果指定了一个相对路径(或者只是一个简单的名字),它们将会在当前加载路径中(load path——$:)的每个目录下搜索这个文件。

使用load或者require所加载的文件,当然也可以包含其他文件,而这些文件又包含别的文件,依次类推,可能不那么明显的是,require是一个可执行的语句——它可能在一个if语句内、或者可能包含一个刚刚拼合的字符串。搜索路径也可以在运行时更改,只需将你希望的目录加入$:数组中。

因为load会无条件地包含源文件,你可以使用它来重新加载一个在程序开始执行厚可能更改的源文件,下面是一个认为设计的示例。

这并不是严格为真的,Ruby在数组$中保存了被require所加载的文件列表,不过,这个列表只包括了调用require时所指定的文件名,欺骗Ruby让它多次加载同一个文件,是有可能。

5.times do |i|
  File.open("temp.rb", "w")
    f.puts "module Temp"
    f.puts " def Temp.var"
  end
  load "temp.rb"
  puts Temp.var
end

对于这个功能,可以考虑一个不太实际的例子:Web应用重新加载正在运行的模块,这让它能动态地更新自己;它不需要重新启动来集成软件的新版本,这是使用例如Ruby等动态语言的众多好处之一。

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

推荐阅读更多精彩内容

  • 一、心得体会1、今天完成了什么? 看了Ruby核心、Duck typing、类与对象、RUby安全 看10个con...
    柳辉阅读 188评论 0 0
  • 一、读书笔记3.4 剩余部分当对象需要访问同类的其他对象的内部状态时,使用保护访问(protected acces...
    柳辉阅读 341评论 0 0
  • 一、心得体会1、今天完成了什么? 20页镐头书 10个controller 回顾以前什么是block?为什么要有b...
    柳辉阅读 436评论 0 0
  • 一、读书笔记3.2 对象和属性 昨天我们学到KaraokeSong是Song的子类,但是我们并没有指定Song类本...
    柳辉阅读 253评论 0 0
  • 一、心得体会1、今天主要完成了什么? 今天主要看了互斥、运行多个线程、Ruby调试器、Ruby基础三章 10个co...
    柳辉阅读 239评论 0 0