单例方法
单例方法(singleton method)是属于单个对象而不是整个类的方法。Ruby 类库中的许多方法都是单例方法。如前所述,这是因为每个类都是 Class 类型的对象。或者,简单地说:每个类的类都是 Class。所有类都是如此 - 无论是你自己定义的类还是 Ruby 类库提供的类:
class MyClass
end
puts( MyClass.class ) #=> Class
puts( String.class ) #=> Class
puts( Object.class ) #=> Class
puts( Class.class ) #=> Class
puts( IO.class ) #=> Class
现在,一些类也有类方法 - 即属于 Class 对象本身的方法。从这个意义上讲,这些是 Class 对象的单例方法。实际上,如果你执行以下代码,将显示一个与 IO 类的类方法名称匹配的方法名称数组:
p(IO.singleton_methods)
如前所述,当你编写自己的类方法时,可以通过在方法名前加上类的名称来实现:
def MyClass.classMethod
事实证明,在为特定对象创建单例方法时,你可以使用类似的语法。这次你在方法名称前加上对象的名称:
def myObject.objectMethod
所有 Ruby 对象都是 Object 类的后代…
…也包括Class
类!初看起来很奇怪,实际上创建对象的每个类本身就是一个从 Object
类继承的对象。要证明这一点,请尝试 class_hierarchy.rb 程序: def showFamily( aClass ) if (aClass != nil) then puts( “#{aClass} :: about to recurse with aClass.superclass = #{aClass.superclass}” ) showFamily( aClass.superclass ) end end让我们看一个具体的示例。假设你有一个程序包含许多不同物种的生物对象(也许你是兽医,或者动物园管理员,或者像本书的作者一样,是一个热情的冒险游戏玩家);每个生物都有一个名为 talk
的方法,它显示每个生物通常发出的声音。
这是我的 Creature 类和一些生物对象:
class Creature
def initialize( aSpeech )
@speech = aSpeech
end
def talk
puts( @speech )
end
end
cat = Creature.new("miaow")
dog = Creature.new("woof")
budgie = Creature.new("Who's a pretty boy, then!")
werewolf = Creature.new("growl")
然后你突然意识到其中一个生物有额外的特殊行为。在满月之夜,狼人(werewolf)不仅会说话(咆哮);它也会嚎叫(“How-oo-oo-oo-oo!”)。它确实需要一个 howl
方法。
你可以回头向 Creature 类添加一个这样的方法,但是你最终也会得到会嚎叫的狗,猫和虎皮鹦鹉 - 这并不是你想要的。你可以创建一个新的继承自 Creature 的 Werewolf 类,但是你只会有一个狼人(唉,它们是濒临灭绝的物种),所以为什么你要创建一个完整的类呢?一个 werewolf 对象除了它有一个 howl
方法之外,与其它生物对象都一样是不是更有意义?好吧,让我们通过给狼人(werewolf)提供它自己的单例方法来做到这一点。来吧:
def werewolf.howl
puts("How-oo-oo-oo-oo!")
end
哎呀,我们可以做得更好!它只会在满月时嚎叫(howls),所以让我们确定如果要求在新月时嚎叫,它就会咆哮(growls)。这是我的完成方法:
def werewolf.howl
if FULLMOON then
puts( "How-oo-oo-oo-oo!" )
else
talk
end
end
请注意,即使此方法已在 Creature 类之外声明,它也可以调用实例方法 talk
。那是因为 howl
方法现在存在于 werewolf 对象内部,因此在该对象中具有与 talk
方法相同的作用域。然而,它不会存在于任何狼人(werewolf)的同伴生物之中; howl
方法属于它并仅属于它一个。尝试执行 budgie.howl
,Ruby 会告诉你 howl
是一个未定义(undefined)的方法。
现在,如果你正在调试供自己使用的代码,那么由于未定义的方法导致程序奔溃可能是可以接受的;但如果你的程序在“终端用户”这个庞大而恶劣的环境中发生这样的情况,那绝对是不可接受的。
如果你认为未定义的方法可能是一个问题,你可以采取避免措施,在使用单例方法之前测试是否存在该单例方法。Object 类有一个 singleton_methods
方法,它返回一个包含单例方法名称的数组。你可以使用 Array 类的 include?
方法来测试方法名称是否包含在数组中。例如,在 singleton_meth2.rb 中,我编写了一个“打开盒子”的游戏,有许多 Box 对象,只有其中一个在打开时获得星星奖励。我已经将这个特殊的 Box 对象命名为 star-prize 并给它一个单例方法 congratulate
:
starprize = Box.new( "Star Prize" )
def starprize.congratulate
puts( "You've won a fabulous holiday in Grimsby!" )
end
当 starprize 盒子被打开时 congratulate
方法会被调用。这段代码(其中 item
是一个 Box 对象)确保了当其它盒子被打开时这个方法(在其它对象中不存在)不会被调用。
if item.singleton_methods.include?("congratulate") then
item.congratulate
end
检查方法有效性的另一种方法是将该方法名称作为符号(以冒号开头的标识符)传递给 Object 类的 respond_to?
方法:
if item.respond_to?(:congratulate) then
item.congratulate
end