单例方法

class_classes.rb

单例方法(singleton method)是属于单个对象而不是整个类的方法。Ruby 类库中的许多方法都是单例方法。如前所述,这是因为每个类都是 Class 类型的对象。或者,简单地说:每个类的类都是 Class。所有类都是如此 - 无论是你自己定义的类还是 Ruby 类库提供的类:

  1. class MyClass
  2. end
  3. puts( MyClass.class ) #=> Class
  4. puts( String.class ) #=> Class
  5. puts( Object.class ) #=> Class
  6. puts( Class.class ) #=> Class
  7. puts( IO.class ) #=> Class

现在,一些类也有类方法 - 即属于 Class 对象本身的方法。从这个意义上讲,这些是 Class 对象的单例方法。实际上,如果你执行以下代码,将显示一个与 IO 类的类方法名称匹配的方法名称数组:

  1. p(IO.singleton_methods)

如前所述,当你编写自己的类方法时,可以通过在方法名前加上类的名称来实现:

  1. def MyClass.classMethod

事实证明,在为特定对象创建单例方法时,你可以使用类似的语法。这次你在方法名称前加上对象的名称:

  1. def myObject.objectMethod
class_hierarchy.rb

所有 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 类和一些生物对象:

singleton_meth1.rb
  1. class Creature
  2. def initialize( aSpeech )
  3. @speech = aSpeech
  4. end
  5. def talk
  6. puts( @speech )
  7. end
  8. end
  9. cat = Creature.new("miaow")
  10. dog = Creature.new("woof")
  11. budgie = Creature.new("Who's a pretty boy, then!")
  12. werewolf = Creature.new("growl")

然后你突然意识到其中一个生物有额外的特殊行为。在满月之夜,狼人(werewolf)不仅会说话(咆哮);它也会嚎叫(“How-oo-oo-oo-oo!”)。它确实需要一个 howl 方法。

你可以回头向 Creature 类添加一个这样的方法,但是你最终也会得到会嚎叫的狗,猫和虎皮鹦鹉 - 这并不是你想要的。你可以创建一个新的继承自 Creature 的 Werewolf 类,但是你只会有一个狼人(唉,它们是濒临灭绝的物种),所以为什么你要创建一个完整的类呢?一个 werewolf 对象除了它有一个 howl 方法之外,与其它生物对象都一样是不是更有意义?好吧,让我们通过给狼人(werewolf)提供它自己的单例方法来做到这一点。来吧:

  1. def werewolf.howl
  2. puts("How-oo-oo-oo-oo!")
  3. end

哎呀,我们可以做得更好!它只会在满月时嚎叫(howls),所以让我们确定如果要求在新月时嚎叫,它就会咆哮(growls)。这是我的完成方法:

  1. def werewolf.howl
  2. if FULLMOON then
  3. puts( "How-oo-oo-oo-oo!" )
  4. else
  5. talk
  6. end
  7. 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

singleton_meth2.rb
  1. starprize = Box.new( "Star Prize" )
  2. def starprize.congratulate
  3. puts( "You've won a fabulous holiday in Grimsby!" )
  4. end

starprize 盒子被打开时 congratulate 方法会被调用。这段代码(其中 item 是一个 Box 对象)确保了当其它盒子被打开时这个方法(在其它对象中不存在)不会被调用。

  1. if item.singleton_methods.include?("congratulate") then
  2. item.congratulate
  3. end

检查方法有效性的另一种方法是将该方法名称作为符号(以冒号开头的标识符)传递给 Object 类的 respond_to? 方法:

  1. if item.respond_to?(:congratulate) then
  2. item.congratulate
  3. end
我们将在第 20 章中讨论处理不存在的方法的另一种方式。