1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

Rubyにおける継承とメッセージング

2022-02-03 22:24:51

継承を利用すると、他のクラスの改良版や特殊化版として機能するクラスを作成することができます。例えば、ジュークボックスのシステムでは、Songクラスに「曲」という概念を内包していますが、市場の拡大に伴い、カラオケのサポートを提供する必要が出てきました。カラオケの曲は、他の曲と変わりません(リードボーカル・トラックがないだけなので、それを気にする必要はありません)。しかし、歌詞のセットと時間情報は含まれています。ジュークボックスがカラオケ曲を再生しているとき、歌詞はジュークボックスの前にあるスクリーン上で音楽とともにスクロールする必要があります。

この問題を解決する一つの方法は、歌詞付きのSongであるKaraokeSongという新しいクラスを定義することです。

class KaraokeSong <Song

  def initialize(name,artist,duration,lyrics)

    super(name,artist,duration)

    @lyrics = lyrics

  end

end



クラス定義の行にある "< Song" は、KaraokeSong が Song のサブクラスであることを Ruby に伝えるものです。したがって、これはSongがKaraokeSongのスーパークラスであることも意味します。

song = KaraokeSong.new("My Way","Sinatra",255,"And now, the ... ")

song.to_s -> *Song:My Way--Sinatra(225)*



to_sメソッドを呼び出すと、歌詞が表示されない

これは、オブジェクトにメッセージを送るときに、どのメソッドを呼び出すかを決めるRubyの仕組みと関係がある。プログラムコードの最初の解析(パース)の際、Rubyはsong.to_sというメソッド呼び出しに遭遇しても、to_sメソッドがどこにあるのかわからず、プログラムの実行が始まるまでその判断を先延ばしにしています。そこでRubyはsongが所属するクラスを調べます。もしそのクラスがメッセージと同じ名前のメソッドを実装していれば、そのメソッドを実行します。そうでなければ、Rubyはその親クラスのメソッドを調べ、さらにその祖先のクラスと、祖先の連鎖をさかのぼっていきます。もし先祖のクラスで正しいメソッドが見つからなければ、Ruby は特別な動作をします(通常はエラーを発生させます)。

この問題をKaraokeSong#to_sを実装することで解決してみましょう。まず、最もグルーブ感のある方法として、Songクラスからto_sメソッドをコピーして歌詞の情報を追加する方法から始めましょう。

class KaraokeSong

 #...

 def to_s

   "KS: #@name--#@artist(#@duration){#@lyrics}"

  end

end

song = KaraokeSong.new("My Way", "Sinatra", 225,"And now, the... ")

song.to_s ->"KS: My Way--Sinatra(225){And now,the...} "



インスタンス変数@lyricsの値を正しく表示できています。しかし、この方法を使うと、サブクラスは祖先のインスタンス変数に直接アクセスする必要があります。では、なぜこれがto_sを実装するのに悪い方法なのでしょうか?

その答えは、良いプログラミングスタイル(デカップリングと呼ばれることもあります)と関係があります。親クラスの内部構造に直接入り込み、インスタンス変数を見せつけるようなことをすると、親の実装に強く縛られることになります。

この問題は、各クラスに実装の詳細を任せることで解決します。KaraokeSong#to_sを呼ぶときは、親クラスのto_sメソッドを呼んで、曲の詳細を取得します。そして、そこに歌詞の情報を付加して結果を返しています。パラメータなしで super を呼び出すと、Ruby は現在のオブジェクトの親クラスにメッセージを送り、子クラスの同じ名前のメソッドを呼び出すように依頼します。これで、新しく改良された to_s メソッドを実装することができます。

class KaraokeSong <Song

  #Format ourselves as a string by appending

  #our lyrics to our parent's to_s value.

  def to_s

    super+"{#@lyrics}"

  end

end

song = KaraokeSong.new("My Way", "Sinatra" ,225, "And now, the... ")

song.to_s ->"Song:My Way--Sinatra(225){And now,the...} "



KaraokeSong は Song のサブクラスであることを Ruby に明示していますが、Song クラスの親クラスが何であるかは明示していません。親クラスを指定せずにクラスを定義すると、Ruby はデフォルトで Object クラスを親として扱います。つまり、すべてのクラスの始祖は Object であり、Object のインスタンスメソッドはすべての Ruby オブジェクトで利用可能です。