Launcherの実装
動作の追加
これから作成するプログラムの主な目的は、指定されたファイル名を受け取り、これを関連するアプリケーションに渡して何らかの処理を行うことです。そこで、ファイル名とアプリケーションのマッピング方法をプログラムに認識させるために、Launcherクラスのインスタンスの作成時に、何らかのマッピングを渡す必要があります。クラスメソッドnewを使ってクラスのインスタンスを作成できることは、既に示したとおりです。最初から何らかのデータセットを含んでいるインスタンスを作成するには、newメソッドに引数を渡します。これを行うには、もちろん、Launcherにコードを追加しなければなりません。
def initialize( app_map ) @app_map = app_map end
Rubyでメソッドを定義するには、defキーワードに続けて、メソッド名と引数リスト(必要な場合)を指定します。引数リストはわかりやすいように括弧で囲まれていますが、コードの意味が明白な場合は省略してもかまいません。
Rubyオブジェクトには、多様な動作が組み込まれていることに注意してください。これをそのまま使用することもできれば、上書きすることもできます。
Launcherをはじめとするすべてのクラスは、Objectクラスを継承し、クラスから作成されるオブジェクトはデフォルトで
initializeメソッドを持つことが決められています。クラスメソッドnewが呼び出されると、まず、目的のオブジェクトに対してリソースが割り当てられ、次に、新しいオブジェクトのinitializeメソッドが呼び出されます。newを介して作成パラメータを提供するには、独自のinitializeメソッドを定義して、新しく作成されたインスタンス内で引数を処理する必要があります。インスタンス変数
initializeメソッドは、app_mapという1つの引数をとります。前述の変数と同様、メソッド引数の型も指定されていません。指定するのはメソッドが1つの引数(app_map)をとるということだけであり、メソッドの本体の中でこの引数は変数@app_mapに割り当てられます。@記号は、変数がインスタンス変数であること、つまり、このオブジェクト内のすべてのコードに対して有効であることを示しています。オブジェクト作成時にこのインスタンス変数を作成すると、コードに追加する他のメソッドでもこの変数を使用できます。
指定されたファイルを関連アプリケーションで実行するためには、プログラムにさらにコードを追加します。
class Launcher def initialize( app_map ) @app_map = app_map end # Execute the given file using the associate app def run( file_name ) application = select_app( file_name ) system( "#{application} #{file_name}" ) end # Given a file, look up the matching application def select_app( file_name ) ftype = file_type( file_name ) @app_map[ ftype ] end # Return the part of the file name string after the last '.' def file_type( file_name ) File.extname( file_name ).gsub( /^\./, '' ).downcase end end
runメソッドはファイル名を引数として受け取り、それをselect_appに渡すことで、実行すべきアプリケーションを見つけます。次に、Rubyのsystemメソッドを使って該当アプリケーションを起動し、ファイル名を渡します。systemメソッドは、指定されたコマンドをサブシェルに送り込んでいるだけです。一方、runメソッドからファイル名を受け取ったselect_appは、file_typeを呼び出して、正規化されたファイル拡張子を取得します。そしてこれを@app_mapへのキーとして使用し、実行すべきアプリケーションを見つけます。
file_typeはファイル名を受け取り、RubyのFileクラスのクラスメソッドを使って拡張子を取得します。extnameで返される文字列には、ピリオド(.)とファイル拡張子が含まれます。これは必要ないため、gsub(global substitute:全置換)を使ってその部分を除外します。残りの部分をdowncaseですべて小文字に変換します。
コンパクトなコードにするために、ここでは上記のメソッドをつなげて記述しています。つまり、File.extnameから返される文字列をgsubが受け取り、gsubから返される文字列をdowncaseが受け取ります。
ここまでのサンプルコードは、StringおよびHashであることが期待されるオブジェクトを使用してきました。しかし、本当に注意すべきは、これらのオブジェクトが特定のメッセージに適切に応答することです。このような小さなプログラムでは、メッセージとランタイム動作に基づく巧妙で強力なオブジェクトシステムの重要性は感じられないかもしれませんが、大規模なRubyプログラムを作成するときには、この点を理解していることが重要です。
オブジェクト、データ型、および動作について(ダックタイピング)
Rubyは、オブジェクト指向プログラミングのメッセージ受け渡しモデルに従います。foo.barのようなコードがある場合、これは、fooで参照されるオブジェクトにメッセージ「bar」が渡されることを意味します。大抵の場合、fooで参照されるオブジェクトにはメソッドbarがあるため、このようなコードを目にすると、これは呼び出し側のfooのbarメソッドだと考えがちです。たしかにこれは便利で一般的な考え方ですが、その裏で何が行われているかを理解することが重要です。
オブジェクトはメッセージを受け取ると、最初に、対応するメソッドを探します。この検索は、オブジェクト自身のクラスから始まってObjectクラスに達するまで、継承階層をさかのぼる形で行われます。一致するものが見つからない場合は、メソッドmethod_missingが呼び出されます。もうおわかりかもしれませんが、method_missingには、例外の発生のみを行うデフォルト実装があります。しかし、デフォルトの初期定義を上書きできるように、method_missingも再定義できます。このメソッドは自由に再定義でき、オブジェクトに機能を追加して、任意のメッセージ要求を処理できるようにしたり、実際よりも多くのメソッドをオブジェクトに実装したりできます。
この柔軟性はRubyの大きな魅力の1つの核を成すものですが、場合によってはトラブルの原因になるという重大な側面もあります。ご存知のように、変数の作成時やメソッド引数リストの宣言時に、データ型の宣言は行われません。しかし、データ型をチェックすることは可能です。コードでオブジェクトの型を確認し、それに応じて処理することができます。例えば、ファイル名(Stringオブジェクトなど)とファイルハンドル(Fileオブジェクトなど)のいずれかを受け取るメソッドを作成するものとします。
ただし、Rubyのコードでは、単なる保護手段としてオブジェクトの型をチェックすることはほとんどなく、指定されたオブジェクトが特定の型であると自ら主張しない限り続行を拒否するということはありません。クラスとオブジェクトは実行時に可変なため、Rubyにおけるデータ型の概念は、基本的に、ある特定時点でのオブジェクトの動作として定義されます。データ型は、その基になるクラスではなく、オブジェクトが応答するメソッドによって定義されます。Ruby愛好者のLogan Capaldoは次のように述べています。「Rubyの世界では、何が親なのかということは問題にならない。重要なのは、何について話しているかということだけだ。」
これを一般的に表す用語として「Duck Typing(ダックタイピング)」があります。これは、「アヒルのように歩き,アヒルのように鳴くなら,それはアヒルである」という考え方です。
バージョン0の仕上げ
この最初のバージョンを、使える状態に仕上げます。ファイルの最後に次のコードを追加してLauncherのインスタンスを作成し、それを使ってアプリケーションを実行します。
def help print " You must pass in the path to the file to launch. Usage: #{__FILE__} target_file " end if ARGV.empty? help exit else app_map = { 'html' => 'firefox', 'rb' => 'gvim', 'jpg' => 'gimp' } l = Launcher.new( app_map ) target = ARGV.join( ' ' ) l.run( target ) end
helpメソッドは必要に応じて指示を出力します。ARGVは引数ベクトルです。組み込みのRubyオブジェクトであり、プログラムに渡されるすべてのパラメータを保持します。これが空の場合、プログラムは何も処理できないため、ヘルプを表示して終了します。そうでなければ、ハッシュオブジェクトを作成し、それを変数app_mapに割り当てます。
{ ... }という表記法は、Hashオブジェクトを作成する場合のRubyのリテラル構文です。Hash.newも使用できますが冗長です。リテラル表記では、=>を使ってハッシュキーを値にマップします。ハッシュはLauncherインスタンスにデータを割り当てるために使われますが、コマンドライン引数は1つの文字列にまとめられて変数targetに格納され、runに渡されます。
このコードを試す前に、正しい実行プログラムを参照するように、app_mapで使用されるアプリケーション値を変更する必要があります。拡張子「rb」をテキストエディタにマップしている場合、次のようなコマンドでコードを試すことができます。
$ ruby launcher.rb launcher.rb
ソースコードがエディタで開きます。
