深入探索

超类

super.rb

要理解 super 关键字的工作原理,请看我们的示例程序,super.rb。它包含五个相关类:Thing 类其他所有类的祖先;从 Thing 类衍生到 Thing2 类,再从 Thing2 类衍生到 Thing3 类,然后依次是 Thing4 和 Thing5 类。

让我们仔细看看这个层次中的前三个类:Thing 类有两个实例变量,@name@description ;Thing2 类也定义了 @fulldescription(一个包含 @name@description 的字符串);Thing3 类则添加了另一个变量 @value

这三个类各自包含一个 initialize 方法,用来在创建新对象时设置变量的值;它们各自也有一个名为 aMethod 的方法,用来改变一个或多个变量的值。后代类 Thing2 和 Thing3 在它们的方法中都使用了 super 关键字。

在命令窗口运行 super.rb 。去按位测试代码,键入 1 到 5的一个数字,当有提示时或者键入 ‘q’ 退出。

在这段代码的底部,我写了一个 “main” 循环,这将在你运行该程序时执行。不要担心不懂这些语法,我们将在以后的章节中学习循环。我已经将 test1test5 添加进了循环,所以你可以很容易地运行包含在不同方法中的代码。首次运行此程序时,在提示符处键入数字 1 然后按 Enter 键。 这将运行包含这两行代码的test1方法:

  1. t = Thing.new( "A Thing", "a lovely thing full of thinginess" )
  2. t.aMethod( "A New Thing" )

第一行在这里创建并初始化 Thing 对象,第二行调用它的 aMethod 方法。因为 Thing 类并非派生自其它类(事实上,所有的 Ruby 类都派生自 Object 类,它是其它所有类的祖先类),所以在这里并没有其它的事情发生。当调用 Thing.initializeThing.aMethod 方法时,输出调用了 inspect 方法来显示对象的内部结构。inspect 方法可以被所有对象调用,是个调试的工具方法。在这里,它给我们显示一个十六进制的数字,来表示这个拥有 @name@description 字符串变量的特定对象。

现在,在提示符下,输入 2 来运行 test2 包含的代码,创建一个 Thing2 对象 t2 并调用 t2.aMethod 方法:

  1. t2 = Thing2.new( "A Thing2", "a Thing2 thing of great beauty" )
  2. t2.aMethod( "A New Thing2", "a new Thing2 description" )

仔细看看输出,你会看到即使 t2 是一个 Thing2 对象,首先被调用的却是 Thing 类的 initialize 方法。想理解为什么会这样,看看 Thing2 类的 initialize 方法的代码:

  1. def initialize(aName, aDescription)
  2. super
  3. @fulldescription = "This is #{@name}, which is #{@description}"
  4. puts("Thing2.initialize: #{self.inspect}\n\n")
  5. end

这里使用了 super 关键字调用了 Thing2 类的祖先类或者说”超类” 的 initialize 方法。你可以从声明中看到 Thing2 类的超类是 Thing 类:

  1. class Thing2 < Thing

在 Ruby 中, 当 super 关键字本身单独使用时(即不带任何参数),它将会把当前方法(这里是 Thing2.initialize)所有参数传递给在其超类中的同名方法(这里是 Thing.initialize)。或者,你可以显式的指定 super 后的参数列表。因此,在本例中以下代码将具有相同的效果:

  1. super(aName, aDescription)

虽然允许单独使用 super 关键字,但是通常为了清楚起见,应该明确指定要传递给超类方法的参数列表。无论如何,如果你想要传递有限的参数,则需要一个明确的参数列表。例如,Thing2 的 aMethod 方法仅将 Name 参数传递给其超类 Thing1 的 initialize 方法:

这解释了为什么 @description 变量在 Thing2.aMethod 方法被调用时为何不会改变。

现在,你看看 Thing3 类,你会看到这增加了一个更多的变量,@valueinitialize 方法传递了两个参数 aNameaDescription 给它的超类 Thing2。反过来,正如我们所看到的,Thing2 类的 initialize 同样地将这些参数传递给了它的超类 Thing 的 initialize 方法。

程序运行时,在提示符下输入 3 以查看输出。这是此次执行的代码:

  1. t3 = Thing3.new("A Thing 3", "a Thing3 full of Thing and Thing2iness",500)
  2. t3.aMethod( "A New Thing3", "and a new Thing3 description",1000)

注意执行流程是如何在类层次结构中向上传递的,在 Thing 中的 initializeaMethod 方法在执行前匹配 Thing2 和 Thing3 中的同名方法。

到目前为止,我做的例程中它们并没有强制重写超类的方法。这只有当你想添加一些新行为的时候才会需要。Thing4 省略了 initialize 方法,但实现了 aMethod 方法。

输入 4 执行下面的代码:

  1. t4 = Thing4.new( "A Thing4", "the nicest Thing4 you will ever see", 10 )
  2. t4.aMethod

当你运行它,发现第一个可用的 initialize 被调用时, Thing4 对象被创建。这恰好是 Thing3.initialize,同时也会再次调用其祖先类 Thing2 和 Thing 的 initialize 方法。然而,Thing4 实现的 aMethod 方法没有调用它的超类方法,因此其它祖先类中的方法会被忽略,这里将立即执行。

最后,Thing5 继承自 Thing4,不会引入任何新的数据或方法。在提示符处输入 5 以执行以下代码:

  1. t5 = Thing5.new("A Thing5", "a very simple Thing5", 40)
  2. t5.aMethod

这次你会看到,调用 new 方法会导致 Ruby 回溯类层次结构,直到找到第一个 initialize 方法。而这存在于 Thing3 中(同样也是 Thing2 和 Thing3 的初始化方法)。然而,aMethod 方法的第一个实现在 Thing4 中,它没有调用 super,所以从这里就结束了。

superclasses.rb

所有的 Ruby 类都是继承自 Object 类。

Object 类本身没有超类,任何尝试获取它的超类行为都会返回 nil 。

begin x = x.superclass puts(x) end until x == nil

类中的常量

有时可能需要访问在类中声明的常量(以大写开头)。让我们假设你有下面这个类:

classconsts.rb
  1. class X
  2. A = 10
  3. class Y
  4. end
  5. end

为了访问常量 A,你需要使用特殊范围运算符 ::,像这样:

  1. X::A

类名是常量,所以这个操作符允许你访问其他类中的类,这使得可以从“嵌套”类创建对象,如类 X 中的类 Y

  1. ob = X::Y.new

局部类

在 Ruby 中,定义一个类在同一个地方是不必要的,如果你愿意,你可以在程序的不同地方定义同一个类。当一个类派生自特定的超类,每个后续的类定义部分使用 < 操作符指定超类是可选的。

这里我创建了两个类,A 以及 派生自 A 的 B:

partial_classes
  1. class A
  2. def a
  3. puts( "a" )
  4. end
  5. end
  6. class B < A
  7. def ba1
  8. puts( "ba1" )
  9. end
  10. end
  11. class A
  12. def b
  13. puts( "b" )
  14. end
  15. end
  16. class B < A
  17. def ba2
  18. puts( "ba2" )
  19. end
  20. end

现在,如果我创建一个 B 对象,A 和 B 的所有方法都是可用的:

  1. ob = B.new
  2. ob.a
  3. ob.b
  4. ob.ba1
  5. ob.ba2

你还可以将功能添加到 Ruby 的标准类中,例如 Array:

  1. class Array
  2. def gribbit
  3. puts( "gribbit" )
  4. end
  5. end

这会将 gribbit 方法添加到 Array 类中,以便现在可以执行以下代码:

  1. [1,2,3].gribbit