builder by ZDNet Japanをご愛読頂きありがとうございます。

builder by ZDNet Japanは2022年1月31日にサービスを終了いたします。

長らくのご愛読ありがとうございました。

Rubyのモジュールとイントロスペクション

文:Steve Hayes(Builder AU) 翻訳校正:村上雅章・野崎裕子
2007-12-13 10:00:00
  • このエントリーをはてなブックマークに追加

 この場合、#first_nameと#last_nameにはデフォルトのアクセス修飾子、すなわちpublicが適用されるため、スクリプトから直接アクセスすることができる。#full_nameはprotectedメソッドであり、protectedメソッドのレシーバはselfと同じクラスでなければならないため(この例におけるselfはObjectであり、レシーバはPersonである)、アクセスすることができない。また、#built_nameはprivateメソッドであり、privateメソッドのレシーバは暗黙のselfでなければならないため、アクセスすることができない。

 では、サブクラスのpublicメソッドを経由してこういったメソッドにアクセスする例を見てみることにしよう。

class OpenPerson < Person
  
  def public_full_name
    self.full_name
  end

  def public_built_name
    built_name
  end
  
end

 OpenPersonの場合、#full_nameのレシーバは明示的なselfであるため、このメソッドにアクセスすることができる。また、#built_nameのレシーバは暗黙のselfであるため、このメソッドにもアクセスすることができる。以下が実行結果である。

person = OpenPerson.new

person.public_full_name # => "Steve Hayes"
person.public_built_name # => "Steve Hayes"

 #full_nameはprotectedであるため、#public_full_name中の明示的なレシーバを削除することもできる。しかし、#built_nameはprivateであるため、#public_built_name中に明示的なレシーバを追加することはできない。では、こういった実装を試してみよう。

class OpenPerson < Person
  
  def public_full_name
    full_name
  end

  def public_built_name
    self.built_name
  end
  
end

 実行すると次のような結果が得られる。


person = OpenPerson.new

person.public_full_name # => "Steve Hayes"
person.public_built_name # => :in `public_built_name': private method `built_name' called for #

 ここまで見てきた時点でprotectedとprivateはとてもよく似たものに見える--なぜどちらも必要なのだろうか?

 protectedメソッドはメソッドパラメータとして引き渡されたオブジェクトに対して呼び出すことができるため、比較やコピーといったさまざまな使用が可能となる。以下はprotectedのPerson#full_nameメソッドを使用した比較メソッドの一例である。

class Person
   #...
  def same_as?(other_person)
    self.full_name.eql?(other_person.full_name)
  end
   #...
end

person = Person.new
person.same_as?(Person.new) # => true

 #full_nameはprotectedであるため、あらゆるオブジェクトに対して使用できるようにはなっていないものの、selfおよびother_person(つまりselfと同じクラス)の双方に対して使用できるのである。#full_nameがprivateである場合、こういったことはできない。つまり、protectedによって適切なアクセス可能性が確保できるのである(ただし多用しすぎてはいけない)。

 ここまでの説明で、Rubyのアクセス修飾子について理解してもらえたはずだ。これで、スクリプティング言語としてのRubyの動作と、実行中のコード構造を取得するためのRubyの使用方法について理解する準備ができたことになる。

 Rubyには、クラスやインスタンスについてのメタ情報を収集するためのメソッドが数多く用意されている。こういったメソッド群はJavaにおけるリフレクションAPIに相当するものの、Rubyのものの方がより高い機能を有しており、かつ直感的な、つまりJavaのリフレクションのような「後付け」感のするものではなく、Ruby言語の延長線上にあるという感覚を抱かせるものとなっている。

 オブジェクトの情報を収集するためのシンプルかつ有益なメソッドに#classと#methodがある。では簡単なRubyスクリプトを実行し、これらのメソッドが返す結果を見てみることにしよう。

self.class # => Object

 スクリプト実行時のコンテキストは、クラスObjectのインスタンスのものとなる。このため、Objectのメソッドであれば何でも使用できるということになる--つまり、publicメソッドであれば明示的な、あるいは暗黙のレシーバに対して起動することができ、privateメソッドであったとしても、暗黙のレシーバに対して用いるのであれば起動することができるのだ。

 では次に、#object_idというもう1つのリフレクションメソッドを見てみることにしよう。このメソッドはアプリケーション内の任意のオブジェクトに対して呼び出すことができ、そのオブジェクト固有の数値識別子を返すというものである。このメソッドもObjectのpublicメソッドであるため、暗黙のレシーバに対しても、明示的なレシーバに対しても起動することができる。以下がその使用例である。

self.object_id # => 107770
object_id # => 107770

 一方、#putsはObjectのprivateメソッドであるため、暗黙のレシーバ(スクリプトを記述する際に重宝する)に対して起動することはできるものの、明示的なレシーバに対して起動することはできない。以下がその例である。

puts 'sample string' # >> ‘sample string’
self.puts 'sample string' # >> private method `puts' called for main:Object

 では、Rubyのコードを組織化するその他の方法を解説する前に、クラス構造の情報を取得する際に重宝するメソッドをもう少し見てみることにしよう。Rubyにおける他のすべてのものと同様に、整数はあるクラスのインスタンスとして実装されている。

3.class # => Fixnum

 しかしFixnumはObjectから直接継承しているわけではない。

Fixnum.superclass # => Integer

 Fixnumの継承階層をすべて表示させるために、以下のような簡単なループを用いたコードを記述することができる。

klass = Fixnum
ancestors = []
while klass
  ancestors << klass
  klass = klass.superclass
end
ancestors # => [Fixnum, Integer, Numeric, Object]

 このコードの実行結果はFixnumの継承階層そのものであるものの、Rubyではこういった情報を取得するための組み込みメソッドとして#ancestorsが用意されている。このメソッドの実行結果を以下に示す。

Fixnum.ancestors # => [Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]

 ちょっと待った、これは先ほどの結果と同じではない!ここで表示されている余計なものはどこから来たのか、そしてこれらはいったい何なのだろうか?次を見てもらいたい。

Fixnum.ancestors.inject({}) {|map, each| map[each] = each.class; map}
=> {Numeric=>Class, Precision=>Module, Fixnum=>Class, Integer=>Class, Kernel=>Module, PP::ObjectMixin=>Module, Comparable=>Module, Object=>Class}

 上記の結果を見ることで、上位階層として表示されたいくつかのものはクラスであり、その他のものはモジュールであるということが判るはずだ。では、モジュールへと話を進めることにしよう。

 Rubyにおけるモジュールというものは、名前空間を提供するために用いられるとともに、継承階層から独立した実装を共有するためにも用いられる。

 では、名前空間を提供するという使用方法から見てみることにしよう。以下は、Personクラスをさらに簡単にしたバージョンである。

class Person
  def name
    'Steve Hayes'
  end 
end

 ここで、現在作成しているアプリケーションのコンポーネントとして、一般人用とは別に有名人用のものがあり、そのコンポーネント内にもPersonを置く必要が出てきたと考えて欲しい。その場合には以下のような方法をとることになる。

module Distinguished
  class Person
    def name
      'Shakespeare'
    end
  end
end

 いずれのクラスもPersonという名前であるものの、1つはデフォルトモジュール内にあり、もう1つはDistinguishedモジュール内にある。これらのクラスを完全修飾したかたちで参照する場合、以下のように記述することになる。

Person.new.name # => "Steve Hayes"
Distinguished::Person.new.name # => "Shakespeare"

 モジュール名の規約はクラス名におけるものと同じである--つまり先頭の文字を大文字にしたキャメル記法に従うことになる。また、モジュールは以下のようにネストすることもできる。

module Very
  module Distinguished
    class Person
      def name
        'Einstein'
      end
    end
  end
end

Very::Distinguished::Person.new.name # => "Einstein"

 モジュールの提供するものが名前空間だけであったとしても、おそらくモジュールは非常に便利なものであったはずだ。しかし実際には、先ほどのFixnumの例で見てもらったように、クラス階層から独立した実装を共有するために用いられていることの方が多いだろう。ここまでで見てきたモジュールにはクラス定義しかなかったものの、モジュールにはメソッド定義も記述することができる。includeキーワードを用いることで、モジュール中のすべてのメソッドを既存クラスにマージし、該当クラスの全インスタンスでそれらを使用できるようにすることができる−−こういったモジュールの用法は一般的に「Mix-in」と呼ばれている。またこのコラムでは、こういったincludeを記述するクラスを「インクルードするクラス」と呼ぶことにする。

 今までに見てきたPersonクラスにおいて、その姓名と明示的なクラス名を含む挨拶文を表示する必要が出てきたと考えて欲しい。Rubyのオブジェクト指向では単一継承を採用しており、すべてのPersonクラスは暗黙のうちにObjectから継承されることになるため、こういった機能を提供するために使用できる親クラスは存在していない(理論上は、Objectに実装することも可能であるが、そういった方法についてはまだ解説していないうえ、あまり良い解決策とは言えない)。ここでMix-inの出番がやってくるのだ!以下はこのMix-inを使用した実装である。

module PersonUtilities
  def greeting
    "Greetings, #{name} (#{self.class.name})"
  end
end
 そして以下は、今までに見てきた3つのPersonクラスを修正したものだ。
class Person
  include PersonUtilities
  def name
    'Steve Hayes'
  end
end

module Distinguished
  class Person
    include PersonUtilities
    def name
      'Shakespeare'
    end
  end
end

module Very
  module Distinguished
    class Person
      include PersonUtilities
      def name
        'Einstein'
      end
    end
  end
end

 #greetingの実装は、このモジュールをインクルードするクラスが提供することになっているメソッド(#name)、およびself(実行時に、インクルードするクラスへと解決される)を参照している点に注意されたい。

 以下は3つのクラスそれぞれに対して#greetingを起動した結果である。

Person.new.greeting # => "Greetings, Steve Hayes (Person)"
Distinguished::Person.new.greeting # => "Greetings, Shakespeare (Distinguished::Person)"
Very::Distinguished::Person.new.greeting # => "Greetings, Einstein (Very::Distinguished::Person)"

ブログの新規登録は、2021年12月22日に終了いたしました。

folllow builer on twitter
このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]