コンテンツにスキップ

継承

クラス階層の頂点であるObjectを覗き、すべてのクラスは他のクラス (スーパークラス) を継承しています。継承元を明示的に指定せず定義した場合、クラスであれば Reference が、構造体であれば Struct がスーパークラスになります。

クラスを継承したとき、すべてのインスタンス変数、およびインスタンスメソッドとクラスメソッドがスーパークラスから引き継がれます。その中にはコンストラクタ (newinitialize) も含まれます。

class Person
  def initialize(@name : String)
  end

  def greet
    puts "Hi, I'm #{@name}"
  end
end

class Employee < Person
end

employee = Employee.new "John"
employee.greet # "Hi, I'm John"

クラスがnewinitialize を定義した場合は、スーパークラスのコンストラクタは継承されません。

class Person
  def initialize(@name : String)
  end
end

class Employee < Person
  def initialize(@name : String, @company_name : String)
  end
end

Employee.new "John", "Acme" # OK
Employee.new "Peter"        # Error: wrong number of arguments for 'Employee:Class#new' (1 for 2)

派生クラスではメソッドをオーバーライドすることが可能です。

class Person
  def greet(msg)
    puts "Hi, #{msg}"
  end
end

class Employee < Person
  def greet(msg)
    puts "Hello, #{msg}"
  end
end

p = Person.new
p.greet "everyone" # "Hi, everyone"

e = Employee.new
e.greet "everyone" # "Hello, everyone"

オーバーライドの代わりに、型制約を利用して派生クラスに特化したメソッドを定義することもできます。

class Person
  def greet(msg)
    puts "Hi, #{msg}"
  end
end

class Employee < Person
  def greet(msg : Int32)
    puts "Hi, this is a number: #{msg}"
  end
end

e = Employee.new
e.greet "everyone" # "Hi, everyone"

e.greet 1 # "Hi, this is a number: 1"

super

super を使うと、スーパークラスのメソッドを実行することが可能です。

class Person
  def greet(msg)
    puts "Hello, #{msg}"
  end
end

class Employee < Person
  def greet(msg)
    super # Same as: super(msg)
    super("another message")
  end
end

Without arguments or parentheses, super receives all of the method's parameters as arguments. そうでない場合には、指定した引数が渡されます。

共変性と反変性

継承が少し厄介なのは、配列の場合です。配列の要素に継承されたものが使われた場合は、十分に注意すべきです。例えば、次のような例を考えてみましょう。

class Foo
end

class Bar < Foo
end

foo_arr = [Bar.new] of Foo  # => [#<Bar:0x10215bfe0>] : Array(Foo)
bar_arr = [Bar.new]         # => [#<Bar:0x10215bfd0>] : Array(Bar)
bar_arr2 = [Foo.new] of Bar # コンパイルエラー

Foo の配列は Foo と Bar を持つことができますが、Bar の配列は Bar とそのサブクラスしか持つことができません。

これは自動キャストが作用しているときに、悩ましい結果を引き起こすことがあります。例えば、次は動作しません。

class Foo
end

class Bar < Foo
end

class Test
  @arr : Array(Foo)

  def initialize
    @arr = [Bar.new]
  end
end

@arrArray(Foo) として宣言したので、このインスタンス変数に対して Bar の配列を代入できるように感じます。しかし、そうはいきません。initialize での [Bar.new] という式の型は Array(Bar) です。そして Array(Bar)Array(Foo) のインスタンス変数に代入することはできません

どのようにするのが正しい方法でしょうか?式の型を正しい型にしましょう。つまり Array(Foo) のように (具体的な方法は以下で)。

class Foo
end

class Bar < Foo
end

class Test
  @arr : Array(Foo)

  def initialize
    @arr = [Bar.new] of Foo
  end
end

この例は特定の型 (配列) と特定の操作 (代入) に対するもので、上記の方法は他の型の場合や代入意外の操作の場合にも同様に適用できるわけではありません。一般的な共変性と反変性は完全にはサポートされていません。