跳至內容

虛擬與抽象類型

當變數的類型在同一個類別階層下結合不同類型時,其類型會變成虛擬類型。這適用於除了 ReferenceValueIntFloat 之外的所有類別和結構。一個範例:

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+

您可以看到 @petAnimal++ 表示它是虛擬類型,意思是「任何繼承自 Animal 的類別,包括 Animal」。

如果類型聯合在同一個階層下,編譯器總是會將其解析為虛擬類型

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

# pet : Animal+

編譯器對於相同階層下的類別和結構總是會這樣做:它會找到所有類型都繼承自的第一個超類別(不包括 ReferenceValueIntFloat)。如果找不到,則類型聯合會保持不變。

編譯器這樣做的真正原因是為了能夠更快地編譯程式,而不是建立各種不同的相似聯合,同時也讓產生的程式碼體積更小。但另一方面,這也是合理的:同一個階層下的類別應該以類似的方式運作。

讓我們讓 John 的寵物說話:

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

我們會收到一個錯誤,因為編譯器現在將 @pet 視為 Animal+,其中包含 Animal。而且由於它找不到 talk 方法,因此會發生錯誤。

編譯器不知道的是,對我們來說,永遠不會實例化 Animal,因為實例化一個 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,編譯器會檢查所有子類別是否都實作此方法(符合參數類型和名稱),即使程式碼未使用它們。

抽象方法也可以在模組中定義,並且編譯器會檢查包含的類型是否實作它們。