継承¶
階層ルートである`Object`クラスを除くすべてのクラスは、別のクラス(スーパークラス)を継承します。指定しない場合、クラスの場合は`Reference`、構造体の場合は`Struct`がデフォルトになります。
クラスは、コンストラクタ(`new`と`initialize`)を含む、スーパークラスのすべてのインスタンス変数とすべてのインスタンスメソッドおよびクラスメソッドを継承します。
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"
クラスが`new`または`initialize`を定義する場合、そのスーパークラスのコンストラクタは継承されません。
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
引数または括弧がない場合、`super`はメソッドのすべてのパラメータを引数として受け取ります。それ以外の場合は、渡した引数を受け取ります。
共変性と反変性¶
継承が少しトリッキーになるのは、配列を使用する場合です。継承が使用されているオブジェクトの配列を宣言する際には注意が必要です。たとえば、次のような場合を考えてみましょう。
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 # compiler error
Foo配列はFooとBarの両方を保持できますが、Barの配列はBarとそのサブクラスのみを保持できます。
自動キャストが作用する場合、これが問題になることがあります。たとえば、次のコードは動作しません。
class Foo
end
class Bar < Foo
end
class Test
@arr : Array(Foo)
def initialize
@arr = [Bar.new]
end
end
`@arr`を`Array(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
これは、1つの型(Array)と1つの操作(代入)についてのみです。上記のロジックは、他の型と代入については異なる方法で適用されます。一般に、共変性と反変性は完全にはサポートされていません。