既存のクラスの編集
Rubyの多様性のポイントは、Rubyのすべてのクラスをメソッドとデータを追加して拡張できるという点です。私はよく、コアRubyクラスのオリジナルのソースコードを拡張するのではなく、自分が開発しているアプリケーション内でクラスを拡張するという方法をとります。JavaやC++開発者にとっては奇妙に思えるかもしれませんが、このテクニックを使うと、プロジェクトのリソースを1か所で管理でき、オリジナルのクラスを「膨張」させることなく多くの開発者がアプリケーション固有の機能を追加できるようになります。Javaプログラマの立場で、Javaのルールによる制約を考えてみてください。Javaでは既存のクラスに機能やデータを追加する場合、サブクラスを作成する必要があります。
次のリストは、String
クラスにstem
メソッドを追加する方法を示しています。
begin # stem is undefined at this point puts "The trips will be longer in the future".downcase.stem rescue puts 'Error:' + $! end require "rubygems" require_gem 'stemmer' class String # you will extend the String class include Stemmable # add methods and data defined in module Stemmable end puts "The trips will be longer in the future".downcase.stem
また、自分のアプリケーション内で、既存のクラスにメソッドや新しいクラスインスタンス変数を追加できると便利なことが分かります。
次の節では、Rubyの強力な柔軟性を証明するもう1つの例である「ダックタイピング」(duck typing)について説明します。
Rubyのダックタイピング
Javaでは、オブジェクトのクラス階層で(publicやpackageなどの可視性を指定して)定義したオブジェクトのメソッドしか呼び出せません。例えば、あるオブジェクトのコレクションがあり、そのコレクション内の各要素に対して、1つ以上のメソッドを呼び出す処理を繰り返す場合を考えてみましょう。Javaでこのような処理を行う場合、対象となるオブジェクトは同じクラス階層に属しているか、呼び出すメソッドを定義しているインターフェイスを実装している必要があります。
話の流れから予想はついていると思いますが、RubyはJavaよりもはるかに柔軟です。Rubyのランタイムのメソッド呼び出しスキームでは、固有のデータ型やクラスは必要ありません。次のように、obj
オブジェクトのfoo
メソッドを呼び出し、この最初のメソッド呼び出しによって取得したオブジェクトからbar
メソッドを呼び出すとします(この例は2つの同じ呼び出しを示しています。メソッドに引数がない場合、()は省略できます)。
obj.foo.bar obj.foo().bar()
obj.foo
を呼び出した結果はオブジェクトになります。そして、この新しいオブジェクトのクラスが何であろうと、そのオブジェクトのbar
メソッドを呼び出すことになります。
もう1つの例として、コレクション内の各オブジェクトからname
というメソッドを呼び出すことを考えます。このコレクション内の1つの要素が、何らかの理由で、name
メソッドが定義されていないMyClass2
クラスのインスタンスになったとしましょう。その場合、このオブジェクトからname
メソッドを最初に呼び出そうとした時点で、ランタイムエラーが発生します。このエラーは、次のようにメソッドを動的に追加することで解決できます。
class MyClass2 def name "MyClass2: #{this}" end end
Javaのような強力な型チェックを持つ言語に慣れている開発者は、コンパイラやインタプリタがすべての型の使用を静的にチェックしない、このような「安全性に欠ける」柔軟性はプログラムの信頼性を低下させると思うようです。しかし、ランタイムの型チェックで発生するプログラムのバグは、テストですばやく検出できるので、ソフトウェアの信頼性は低下しません。それよりも、言語がより柔軟であるというメリットによって、プログラム自体の長さも開発時間も短くできます。
不明なメソッドの処理
ダックタイピングなんて信頼できないとまだ思っているでしょうか? ではもう1つ、Rubyの裏技を紹介しましょう。Rubyクラスで不明メソッドを処理する方法です。以下は、2つのメソッド(定義済みのlength
と未定義のfoobar
)を文字列オブジェクトに適用する簡単な例です。
markw$ irb >> s = "this is a string" => "this is a string" >> s.length => 16 >> s.foobar NoMethodError: undefined method `foobar' for "this is a string":String from (irb):3
未定義のメソッドの場合、エラーがスローされます。そこで、独自に作成したmethod_missing
メソッドをString
クラスに「貼り付け」ます。
>> class String >> def method_missing(method_name, *arguments) >> puts "Missing #{method_name} (#{arguments.join(', ')})" >> end >> end => nil >> s.foobar Missing foobar () => nil >> s.foobar(1, "cat") Missing foobar (1, cat) => nil >>
Rubyのランタイムシステムは、オブジェクトのメソッドを検出できない場合、必ず初期設定で継承されているmethod_missing
を呼び出し、単にNoMethodError例外を発生させます。この例では、この継承されているメソッドをエラーをスローしないメソッドでオーバーライドし、メソッド呼び出しの名前と引数を出力しています。次の例では、このメソッドをさらに再定義しました。今度は、メソッド名がfoobar
であるかどうかを確認するためのチェックを定義しています(メソッド名は評価時にto_s
を使って文字列に変換しています)。
>> class String >> def method_missing(method_name, *arguments) >> if method_name.to_s=='foobar' >> arguments.to_s.reverse # return a value >> else ?> raise NoMethodError, "You need to define #{method_name}" >> end >> end >> end => nil >> s.foobar(1, "cat") => "tac1" >> s.foobar_it(1, "cat") NoMethodError: You need to define foobar_it from (irb):38:in `method_missing' from (irb):43 from :0 >>
メソッド名がfoobar
に等しい場合は戻り値が返され、等しくない場合はエラーがスローされます。