跳至内容

虚拟和抽象类型

当变量的类型在同一类层次结构下组合了不同的类型时,它的类型就变成了一个 **虚拟类型**。这适用于除 `Reference`、`Value`、`Int` 和 `Float` 之外的所有类和结构。一个例子

class Animal
end

class Dog < Animal
  def talk
    "Woof!"
  end
end

class Cat < Animal
  def talk
    "Miau"
  end
end

class Person
  getter pet

  def initialize(@name : String, @pet : Animal)
  end
end

john = Person.new "John", Dog.new
peter = Person.new "Peter", Cat.new

如果您使用 `tool hierarchy` 命令编译上面的程序,您将看到 `Person` 的以下内容

- class Object
  |
  +- class Reference
     |
     +- class Person
            @name : String
            @pet : Animal+

您可以看到 `@pet` 是 `Animal+`。`+` 表示它是一个虚拟类型,意思是“任何从 `Animal` 继承的类,包括 `Animal` 本身”。

编译器始终会将类型联合解析为虚拟类型,前提是它们在同一层次结构下

if some_condition
  pet = Dog.new
else
  pet = Cat.new
end

# pet : Animal+

编译器始终会对同一层次结构下的类和结构执行此操作:它将找到第一个所有类型都从其继承的超类(不包括 `Reference`、`Value`、`Int` 和 `Float`)。如果找不到,类型联合将保持不变。

编译器执行此操作的真正原因是为了能够通过不创建所有类型的不同类似联合来更快地编译程序,同时使生成的代码体积更小。但另一方面,它是有意义的:同一层次结构下的类应该以类似的方式表现。

让我们让约翰的宠物说话

john.pet.talk # Error: undefined method 'talk' for Animal

我们收到一个错误,因为编译器现在将 `@pet` 视为 `Animal+`,其中包括 `Animal`。由于它在其中找不到 `talk` 方法,因此出现错误。

编译器不知道的是,对我们来说,`Animal` 永远不会被实例化,因为实例化它没有意义。我们有一种方法可以告诉编译器,通过将类标记为 `abstract`

abstract class Animal
end

现在代码可以编译了

john.pet.talk # => "Woof!"

将类标记为抽象将同时阻止我们创建它的实例

Animal.new # Error: can't instantiate abstract class Animal

为了更明确地表明 `Animal` 必须定义一个 `talk` 方法,我们可以将其添加到 `Animal` 中,作为一个抽象方法

abstract class Animal
  # Makes this animal talk
  abstract def talk
end

通过将方法标记为 `abstract`,编译器将检查所有子类是否实现了此方法(匹配参数类型和名称),即使程序没有使用它们。

抽象方法也可以在模块中定义,编译器将检查包含的类型是否实现了它们。