16. 类,对象,变量
• 对象和属性
class Song
def name
@name
end
def artist
@artist
end
def duration
@duration
end
end
Ruby 专门提供了一个简便的形式 attr_reader 来帮你创建这样的访问方法
class Song
attr_reader :name, :artist, :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.artist >> "Fleck"
aSong.name >> "Bicylops"
aSong.duration >> 260
:artist 这种结构返回对应 artist 的对象标识,你可以把 :artist 当作是变量 artist 的
名字,而 artist 是变量的值
17. 类,对象,变量
• 可写属性
class JavaSong { // Java code
private Duration myDuration;
public void setDuration(Duration newDuration) {
myDuration = newDuration;
}
}
s = new Song(....)
s.setDuration(length)
class Song
def duration=(newDuration)
@duration = newDuration
end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration >> 260
aSong.duration=257 # set attribute with updated value
aSong.duration >> 257
实际上,以等号结尾的方法定义让方法名能够出现在赋值语句左边
Ruby 也提供了创建写属性方法的快捷方式。
class Song
attr_writer :duration
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.duration = 257
18. 类,对象,变量
• 虚拟属性
class Song
def durationInMinutes
@duration/60.0 # force floating point
end
def durationInMinutes=(value)
@duration = (value*60).to_i
end
end
aSong = Song.new("Bicylops", "Fleck", 260)
aSong.durationInMinutes >> 4.333333333
aSong.durationInMinutes = 4.2
aSong.duration >> 252
这里我们用属性访问方法创建了一个虚拟的实例变量。对于这个类的外部而言,
durationInMinutes 和其它的属性没有什么区别,但并不存在和它对应的实例变量。
这不只是有趣而已,在 Bertrand Meyer 划时代的著作《 Object-
Oriented Software Construction 》中,称之为统一访问原则
( Uniform Access Principle )。通过隐藏实例变量和实际计算值的不同,你就不
必自己来实现这些处理,等到将来也可以很方便地修改它的工作方式同时又不用
顾及成千上万使用你的类的文件了,这无疑是一场伟大的胜利。
19. 类,对象,变量
• 类变量和类方法
类变量在类的所有对象中是共享的(就像 JAVA 中类的 static 变量),并且可以
被我们下文要提到的类方法访问。对于一个给定的类,特定的类变量只有一个拷
贝。类变量的命名以两个“ @” 开头,就像“ @@count” ,与全局变量和实例变量
不同,类变量在使用前必须要初始化
有时,一个类需要提供不依赖于任何特定实例对象的方法。
我们已经碰到过这样的方法, new 方法创建一个新的 Song 对象,但和特定的歌
曲没有什么关系。
aSong = Song.new(....)
类方法与实例方法的定义方式不同。定义类方法要在方法名前加上类名和一个点
号 (".") 。
class Example
def instMeth # 实例方法
end
def Example.classMeth # 类方法
end
end
20. 类,对象,变量
• 单例与其它构造器
有时你可能会想要重写 Ruby 的默认构造方法,我们仍旧用点唱机来做例子。因为我们在全国各处有很多点唱机,于是就想要让维护工作变得简单一些。
我们需要把在点唱机上发生的事件以日志形式全部记录下来,像被点唱的歌曲,收到的钱,可疑的流量等等,为了节约带宽,这些日志保存在本地,我
们需要一个类来处理这些日志,但是,我们希望一个点唱机只有一个日志对象,还希望所有使用这个日志对象的其它对象共享它(译者注:意即所有对
象只使用一个日志对象)。
通过使用《设计模式》中提到的单例模式,要做到上述这些要求就只有一种办法来创建日志对象,调用 Logger.create 方法,还要确保只有一个日志对
象被创建。
class Logger
private_class_method :new
@@logger = nil
def Logger.create
@@logger = new unless @@logger
@@logger
end
end
把 Logger 的 new 方法声明为私有来防止其他人调用默认构造器创建日志对象,取而代之,我们提供了一个类方法 Logger.create ,它使用类变量
@@logger 来保持对一个单个的日志对象的实例的引用,每次调用这个方法时都返回这个实例。(我们这里介绍的单例的引用并不是线程安全的,如果
多个线程同时运行,就有可能会创建多个日志对象。我们可以使用 ruby 提供的单例混合,而不必自己处理线程安全问题。)我们可以查看方法返回的
对象标识符来观察一下。
Logger.create.id >> 537766930
Logger.create.id >> 537766930
使用类方法来伪造构造器,也可以让那些使用你的类的人倍感轻松。举一个小例子, Shape 类描述正多边形,创建 Shape 类的实例需要提供边数和总
的周长给构造器。
class Shape
def initialize(numSides, perimeter)
# ...
end
end
但是,几年以后这个类用在另外的程序中,程序员需要通过指定名字和边的长度来创建多边形而不是用周长,这时只需要简单地向 Shape 类中添加一
些类方法。
class Shape
def Shape.triangle(sideLength)
Shape.new(3, sideLength*3)
end
def Shape.square(sideLength)
Shape.new(4, sideLength*4)
end
end
26. 容器,代码块,迭代器
• 容器
我们从一个基本的 initialize 方法开始我们的类,创建一个数组用来存放歌曲和一个引用它的实例变量 @songs 。
class SongList
def initialize
@songs=Array.new
end
end
SongList#append 方法在 @songs 数组末尾添加歌曲,返回它自己也就是当前的 SongList 对象。这是一个有用的特性,可以让我们把多个 append 调用
联接在一起,后面会看到这个例子。
class SongList
def append(aSong)
@songs.push(aSong)
self
end
end
然后添加 deleteFirst 和 deleteLast 方法,简单地用 Array#shift 和 Array#pop 来分别实现。
class SongList
def deleteFirst
@songs.shift
end
def deleteLast
@songs.pop
end
end
下一个方法是 [] ,通过索引来访问元素。如果索引是整数(在这里我们用 Object#kind of? 来检查),那么返回该位置的元素。
class SongList
def [](key)
if key.kind_of?(Integer)
@songs[key]
else
# ...
end
end
end
现在需要添加通过歌曲标题来索引的功能,这要求扫描整个歌曲列表,检查每一首歌曲标题。我们需要先来熟悉一下 Ruby 最简洁的一个特性:迭代器。
27. 容器,代码块,迭代器
• 代码块和迭代器
class SongList
def [](key)
if key.kind_of?(Integer)
return @songs[key]
else
for i in 0...@songs.length
return @songs[i] if key == @songs[i].name
end
end
return nil
end
end
它能工作,但能不能做的更自然些呢?这里 for 循环要求数组的一些私有信息,它要求数组的长度,然后按序匹配每一个值。为
什么不要求数组仅提供一个对其每个元素的检测呢?这正是 Array 的 find 方法所做的。
class SongList
def [](key)
if key.kind_of?(Integer)
result = @songs[key]
else
result = @songs.find { |aSong| key == aSong.name }
end
return result
end
end
我们还可以把 if 用作语句修饰符来缩短句子。
class SongList
def [](key)
return @songs[key] if key.kind_of?(Integer)
return @songs.find { |aSong| aSong.name == key }
end
end
find 方法是一个迭代器,一个重复调用代码块的方法。迭代器和代码块是 Ruby 最有趣的特性中的两个,所以我们花些时间来研
究一下它们(这个过程中我们也可以看到我们的 [] 方法是如何真正工作的)。
30. 容器,代码块,迭代器
• 代码块和迭代器
事务代码块
代码块可以定义为在某种事务控制下运行的一系列代码,举例来说,你经常要打开一个文件,对文件的内容进行一些处理,然后
确保在使用完毕后关闭了它。粗略的实现(忽略了错误处理):
class File
def File.openAndProcess(*args)
f = File.open(*args)
yield f
f.close()
end
end
File.openAndProcess("testfile", "r") do |aFile|
print while aFile.gets
end
结果:
This is line one
This is line two
This is line three
And so on...
一旦文件被打开, OpenAndProcess 调用 yield ,把打开的文件对象传递给代码块。当代码块返回后,文件被关闭,这样,关闭
的责任就从文件对象的使用者转移给文件本身了。这种文件管理自己的生命周期的技巧非常有用,所以 Ruby 的 File 类直接就支
持了这种特性。如果 File.open 有一个关联的代码块,那么这个代码块将会随一个文件对象而被调用,到代码块终止时文件对象
被关闭。这很有趣,意味着 File.open 有两种不同的行为。
class File
def File.myOpen(*args)
aFile = File.new(*args)
# 如果有代码块,传递文件,然后等代码块返回时关闭文件
if block_given?
yield aFile
aFile.close
aFile = nil
end
return aFile
end
end