クラスの「継承」と呼ばれる機能を使うと、あるクラスの機能を受け継いだ新たなクラスを作成できます。継承先のクラスは継承元のクラスのインスタンス変数やメソッドを、そっくりそのまま使えます。
rabbit.rbをさらに充実させていきましょう。以下の例は、Rabbitクラスに、耳をターミナルに出力する「Rabbit#print_ears」メソッドを追加し、Rabbitクラスを継承したLopEarクラスを定義する例です。
- class Rabbit
- attr_accessor :name
- attr_reader :color, :length_of_ears
- def initialize(name: "usachan", color: :white, length_of_ears: 10)
- @name = name
- @color = color
- @length_of_ears = length_of_ears
- end
- def jump
- puts "pyon! pyon!"
- end
- def pound_steamed_rice_into_rice_cake
- puts "pettan! pettan!"
- end
- def say_name
- puts "Hello, I'm #{@name}!"
- end
- def print_ears
- puts "∩_∩"
- end
- end
- class LopEar < Rabbit
- def print_ears
- puts "∪ ̄∪"
- end
- end
- require_relative "rabbit"
- rabbit = Rabbit.new
- lop = LopEar.new(name: "lopchan")
- [rabbit, lop].each do |r|
- r.say_name
- r.print_ears
- end
rabbit.rbの28行目以降が、たれ耳のウサギを表すLopEarクラスを定義している部分です。垂れ耳のウサギは、ウサギの一般的な性質を持っていることが期待されるので、継承で実装することにしました。「class LopEar < Rabbit」というように書くと、「そのクラス定義はRabbitクラスを継承したLopEarクラスの定義である」という意味になります。
LopEarクラスの中では、「print_ears」というメソッドを定義しています。継承先のクラスで新たなメソッドを定義することもできますし、この例のように継承元のクラスに定義されているメソッドを上書きすることもできます。このように、継承元のクラスのメソッドを上書きすることを、メソッドの「オーバーライド」と呼びます。
main04.rbでは、LopEarクラスのオブジェクトの振る舞いについて確かめています。3行目と4行目でRabbitクラスのオブジェクトとLopEarクラスのオブジェクトを生成し、6行目から9行目で、それぞれのオブジェクトのsay_nameメソッドとprint_earsメソッドを実行しています。
では、main04.rbを実行してみましょう。
$ ruby main04.rb Hello, I'm usachan! ∩_∩ Hello, I'm lopchan! ∪ ̄∪
LopEarクラスはRabbitクラスを継承しているので、LopEarクラスのオブジェクトは、nameといったインスタンス変数やsay_nameといったメソッドを利用できます。また、上書きしたprint_earsメソッドを実行した場合は、LopEarクラス側での定義が利用されます。
ここまで、何げなくRabbitクラスを定義して、そのオブジェクトを利用してきましたが、実はRabbitクラスはRubyの組み込みクラスであるObjectクラスを継承しています。rabbit.rbと同じ階層のディレクトリでpryを起動して確かめてみましょう。
[1] pry(main)> require_relative "rabbit" => true [2] pry(main)> Rabbit.superclass => Object
[1]ではrequire_relativeを使ってrabbit.rbを読み込んでいます。また、[2]のようにクラスに対して「superclass」メソッドを使うと継承元の親クラスを得ることができ、Objectクラスであることが分かります。Objectクラスにはオブジェクトに共通する基本的なメソッドが定義されており、例えば、to_sメソッドもその一部です。
[3] pry(main)> Rabbit.new.to_s => "#<Rabbit:0x007fb164a72ed0>"
[3]では、Rabbitクラスのオブジェクトを生成して、「to_s」メソッドを呼び出しています。rabbit.rbにto_sメソッドの定義がないのに、きちんと文字列が返っていることに注意してください。これは、Objectクラスでto_sが定義されているからです。
ここで、Arrayクラスのオブジェクト、つまり配列について、to_sの動作を見てみましょう。
[4] pry(main)> ["Alice", "in", "Wonderland"].to_s => "[\"Alice\", \"in\", \"Wonderland\"]"
配列の場合は、配列の内容が人間に分かりやすい文字列として出力されています。これは、Arrayクラスでto_sメソッドをオーバーライドしているからです。Rabbitクラスでもto_sメソッドをオーバーライドしてみましょう。
- class Rabbit
- attr_accessor :name
- attr_reader :color, :length_of_ears
- def initialize(name: "usachan", color: :white, length_of_ears: 10)
- @name = name
- @color = color
- @length_of_ears = length_of_ears
- end
- def jump
- puts "pyon! pyon!"
- end
- def pound_steamed_rice_into_rice_cake
- puts "pettan! pettan!"
- end
- def say_name
- puts "Hello, I'm #{@name}!"
- end
- def print_ears
- puts "∩_∩"
- end
- def to_s
- "名前: #{@name}, 毛の色: #{@color}, 耳の長さ: #{@length_of_ears}"
- end
- end
- class LopEar < Rabbit
- def print_ears
- puts "∪ ̄∪"
- end
- end
[1] pry(main)> require_relative "rabbit" => true [2] pry(main)> Rabbit.new.to_s => "名前: usachan, 毛の色: white, 耳の長さ: 10" [3] pry(main)> LopEar.new(name: "pyonkichi", color: :brown, length_of_ears: 5).to_s => "名前: pyonkichi, 毛の色: brown, 耳の長さ: 5"
[1]でrabbit.rbを読み込み、[2]および[3]でRabbitクラスのオブジェクトとLopEarクラスのオブジェクトを生成し、to_sメソッドを呼び出しています。ここでは、to_sメソッドをオーバーライドしているので、きちんと名前などの情報が文字列として返っています。
ここまでの例ではインスタンス変数のみを使ってきましたが、クラス変数やクラス定数といったものを使うこともできます。
クラス変数は、その名の通りクラス自体に設定できる変数で、継承先のクラスから参照や代入が可能で、クラスのオブジェクトからでも参照や代入ができます。
クラス定数も、クラス変数と同様にクラス自身に設定される定数です。クラス定数はクラスの外から参照することもできます。
では、RabbitクラスとLopEarクラスにクラス変数とクラス定数を追加してみましょう。
- class Rabbit
- attr_accessor :name
- attr_reader :color, :length_of_ears
- @@count = 0
- DEFAULT_NAME = "usachan"
- DEFAULT_COLOR = :white
- DEFAULT_LENGTH_OF_EARS = 10
- DESCRIPTION = "ウサギは特徴的な耳を持つ可愛い動物です。"
- def initialize(name: DEFAULT_NAME, color: DEFAULT_COLOR, length_of_ears: DEFAULT_LENGTH_OF_EARS)
- @name = name
- @color = color
- @length_of_ears = length_of_ears
- @@count += 1
- end
- def jump
- puts "pyon! pyon!"
- end
- def pound_steamed_rice_into_rice_cake
- puts "pettan! pettan!"
- end
- def say_name
- puts "Hello, I'm #{@name}!"
- end
- def print_ears
- puts "∩_∩"
- end
- def print_description
- puts "#{DESCRIPTION}"
- end
- def to_s
- "名前: #{@name}, 毛の色: #{@color}, 耳の長さ: #{@length_of_ears}, 全体でのウサギの数: #{@@count}"
- end
- end
- class LopEar < Rabbit
- DESCRIPTION = "ロップイヤーはウサギであり、耳が垂れている品種の総称です。"
- def print_ears
- puts "∪ ̄∪"
- end
- def print_class_constants_and_variable
- puts "DEFAULT_NAME: #{DEFAULT_NAME}, DEFAULT_COLOR: #{DEFAULT_COLOR}, DEFAULT_LENGTH_OF_EARS: #{DEFAULT_LENGTH_OF_EARS}"
- puts "DESCRIPTION: #{DESCRIPTION}"
- puts "@@count: #{@@count}"
- end
- end
rabbit.rbの5行目の「@@count = 0」の部分が、Rabbitクラスのクラス変数「count」に初期値「0」を設定している部分です。また、7行目から11行目にかけて、クラス定数をいくつか設定しています。クラス定数は、一般的な定数と同様に大文字から始まります。
「@@count」は、「Rabbit#initialize」メソッドが呼ばれたとき、つまりオブジェクトが生成されたときに1を足しています(17行目)。オブジェクトが生成されるたびに1ずつ値が増えるので、全体のウサギの数を表せるというわけです。
また、クラス定数の性質を確認するために、36行目から38行目にかけて、「Rabbit#print_description」というメソッドを定義しました。このメソッドは、単純にクラス定数DESCRIPTIONを標準出力に出力するだけの簡単なものです。
また、46行目にLopEarのクラス定数として、Rabbitクラスのクラス定数DESCRIPTIONを上書きし、52行目から56行目にかけて、一連のクラス定数やクラス変数を標準出力に出力するメソッド、「LopEar#print_class_constants_and_variable」を定義しました。
- require_relative "rabbit"
- rabbit = Rabbit.new
- puts rabbit.to_s
- lop = LopEar.new
- puts lop.to_s
- lop.print_class_constants_and_variable
- rabbit.print_description
- lop.print_description
main05.rbは、これら一連のクラス定数とクラス変数の動きを確認するコードです。以下に、その出力結果を示します。
$ ruby main05.rb 名前: usachan, 毛の色: white, 耳の長さ: 10, 全体でのウサギの数: 1 名前: usachan, 毛の色: white, 耳の長さ: 10, 全体でのウサギの数: 2 DEFAULT_NAME: usachan, DEFAULT_COLOR: white, DEFAULT_LENGTH_OF_EARS: 10 DESCRIPTION: ロップイヤーはウサギであり、耳が垂れている品種の総称です。 @@count: 2 ウサギは特徴的な耳を持つ可愛い動物です。 ウサギは特徴的な耳を持つ可愛い動物です。
3行目でRabbitクラスのオブジェクトを変数「rabbit」に代入し、4行目でrabbitオブジェクトに対して「to_s」メソッドを呼び出しています。また、6行目でLopEarクラスのオブジェクトを変数「lop」に代入し、7行目で「to_s」メソッドを呼び出しています。クラス変数は継承されても共有され、2回目の「to_s」ではウサギの数が1増えていることが分かります。
また9行目では、「LopEar#print_class_constants_and_variable」を呼び出して、一連のクラス変数やクラス定数を出力しています。rabbit.rbの46行目でクラス定数「DESCRIPTION」を上書きしているので、「DESCRIPTION」にはロップイヤーの説明が設定されていることが分かります。
ただし注意したいのは、12行目のように、継承先のクラスのオブジェクトが継承元のオブジェクトのメソッドを呼ぶ場合です。Rabbitクラスの中で定義されている「print_description」メソッドはクラス定数「DESCRIPTION」を使いますが、継承元のRabbitクラスの「DESCRIPTION」が使われていることに留意してください。
Copyright © ITmedia, Inc. All Rights Reserved.
Coding Edge 記事ランキング