Rubyのリファレンスマニュアル(バージョンは2.1.0)によれば、Rubyの呼び出し制限(英語ではAccess Control)には、publicとprivate、protectedの3通りあります。その内容は次のとおり。
- publicに設定されたメソッドは制限なしに呼び出せる。
- privateに設定されたメソッドは関数形式でしか呼び出せない。
- protectedに設定されたメソッドは、そのメソッドを持つオブジェクトがselfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せる。
publicについては問題ないとしても、privateとprotectedの違いは、上記の説明だけではなかなか分かり難いと思います。そこで、テスト用のプログラムを作り、具体的な動作を確認しながら両者の違いを検証してみました。
呼び出し制限privateの本質
先ずは、privateに設定されたメソッドについて考えるため、次のようなクラスを2つ定義します。ファイル名はsamples_private.rbとし、これをrubyのパスの通っている場所に保存します。
class Sample1
def call_sample1
sample1 # 関数形式での呼び出し
end
def call_sample1_with_receiver
self.sample1 # レシーバー(self)形式での呼び出し
end
private
def sample1
p "只今privateメソッドテスト中!"
end
end
class Sample2
def call_sample1_from_sample2
s1 = Sample1.new
s1.call_sample1 # レシーバー形式での呼び出し
end
def call_sample1_from_sample2_without_receiver
s1 = Sample1.new
call_sample1 # 関数形式での呼び出し
end
end
このファイル内のコメントにあるように、関数形式とは、レシーバーを指定しない形式のことをいいます。
本来Rubyでは、レシーバーなしでのメソッド呼び出しはできません。このため、自クラス及びサブクラス内のメソッドを呼び出すためには、selfというレシーバーを用いなければならないのですが、例外的にselfレシーバーだけは省略できます。この省略形を関数形式と呼んでいます。
そこで、Sample1クラスでオブジェクトを作り、関数形式とレシーバー形式で、privateに設定されたsample1メソッドを呼び出すプログラムを作って検証してみました。
ファイル名はtest1_private.rbで、samples_private.rbと同じ階層に保存しました。
require_relative 'samples_private'
s1 = Sample1.new
s1.call_sample1
s1.call_sample1_with_receiver
test1_private.rbをターミナル上から実行します。
ruby test1_private.rb
実行結果は次のとおり。
1行目に、”只今privateメソッドテスト中!”とあるので、privateメソッドであるsample1が関数形式で呼び出され、これが実行されたということが分かります。
これに対して、2行目以降には、レシーバー形式の呼び出しに対するエラーメッセージが表示されています。(NoMethodError)とあることから、レシーバー形式の呼び出しが拒否されたということが分かります。
これにより、リファレンスマニュアルの「privateに設定されたメソッドは関数形式でしか呼び出せない」という原則が守られていることが確認できました。
次に、Sample2クラスからメソッド定義式を経由したメソッドの呼び出しをテストしてみます。実行プログラムの内容は次のとおりで、ファイル名はtest2_private.rbとし、samples_private.rbと同じ階層に保存しました。
require_relative 'samples_private'
s2 = Sample2.new
s2.call_sample1_from_sample2
s2.call_sample1_from_sample2_without_receiver
test2_private.rbをターミナルで実行。
ruby test2_private.rb
実行結果は、次のとおり。
1行目に、”只今privateメソッドテスト中!”とあるので、一見すると、レシーバー形式の呼び出しでprivateに設定された筈のsample1メソッドが実行されているようにも見えますが、s1レシーバーによって呼び出されているのは、あくまでもcall_sample1メソッドです。sample1メソッドそのものは、call_sample1メソッド内から、関数形式で呼び出されているに過ぎません。よって、「privateに設定されたメソッドは関数形式でしか呼び出せない」という原則はここでも守られていることが分かります。
次に、2行目以降についてですが、こちらもtest1_private.rbのときと同じくエラーメッセージが表示されています。ただ、よく見ると、こちらは、(NameError) とあり、test1_private.rbの実行結果に表示されていた (NoNameErro) とは微妙に違っています。
前述のとおり、関数形式の呼び出しにはselfレシーバーが省略されており、samples_private.rbファイルの28行目は、self.call_sample1と書き換え可能です。そうなると、これは、Smple2クラス内のcall_sample1メソッド(または変数)を参照するという意味となります。call_sample1メソッドはSmple2クラス内ではなく、Smple1クラス内にあるため、そのようなメソッドはないというエラーメッセージが表示されたというわけです。
呼び出し制限protectedの本質
次は、protectedに設定されたメソッドについて検証してみます。先ほど作成したsamples_private.rbの11行目から15行目を次のように変更して、rubyのパスの通っている場所に別名保存します。ファイル名はsamples_protected.rbとしました。
class Sample1
def call_sample1
sample1 # 関数形式での呼び出し
end
def call_sample1_with_receiver
self.sample1 # レシーバー(self)形式での呼び出し
end
protected
def sample1
p "只今protectedメソッドテスト中!"
end
end
class Sample2
def call_sample1_from_sample2
s1 = Sample1.new
s1.call_sample1 # レシーバー形式での呼び出し
end
def call_sample1_from_sample2_without_receiver
s1 = Sample1.new
call_sample1 # 関数形式での呼び出し
end
end
続いて、test1_private.rbの1行目をrequire_relative ‘samples_protected’と変更して、samples_protected.rbと同じ階層に別名保存します。ファイル名はtest1_protected.rbとしました。
require_relative 'samples_protected'
s1 = Sample1.new
s1.call_sample1
s1.call_sample1_with_receiver
test1_protected.rbをターミナル上から実行。
ruby test1_protected.rb
実行結果は次のとおり。
これにより、protectedメソッドは、関数形式でもレシーバー形式でも呼び出し可能だということが分かります。
次に、test2_private.rbの1行目をrequire_relative ‘samples_protected’と変更して、samples_protected.rbと同じ階層に別名保存します。ファイル名はtest2_protected.rbとしました。
require_relative 'samples_protected'
s2 = Sample2.new
s2.call_sample1_from_sample2
s2.call_sample1_from_sample2_without_receiver
これをターミナル上から実行します。
ruby test2_protected.rb
実行結果は次のとおり。
test2_private.rbの実行結果とほとんど同じです。一行目に”只今protectedメソッドテスト中!”とあり、2行目以降にエラーメッセージが表示されています。エラーメッセージ中に (NameErro) とあるので、その点もtest2_private.rbの場合と同じです。
これで、「protectedに設定されたメソッドは、そのメソッドを持つオブジェクトがselfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せる」いう原則が、部分的に確認できました。あとは、instance_evalを経由した呼び出しを試せば、完璧です。
instance_evalメソッドとは、{}で囲まれたブロックをレシーバーのインスタンスの元で実行できるメソッドのことですが、詳しく説明するより、実際に実行プログラムを動かしてみた方が分かりやすいので、次のようなプログラムを新規で作成します。ファイル名はtest3_protected.rbで、同じ階層にsamples_protected.rbがあるものとします。
require_relative 'samples_protected'
s1 = Sample1.new
s1.instance_eval{sample1} # 関数形式での呼び出し
s1.instance_eval{self.sample1} # レシーバー(self)形式での呼び出し
instance_evalメソッドの{}で囲まれたブロックに、インスタンス変数やメソッド定義なども記述できますが、今回のように、レシーバーで指定したクラス内のメソッド名を記述して直接実行することも可能です。
念のため、関数形式とselfレシーバーを明示した形式の両方で、sample1メソッドを呼び出してみましたが、実行結果は、test1_protected.rbと同じく、”只今protectedメソッドテスト中!”というメッセージが2行連続で表示されます。
これにより、「protectedに設定されたメソッドは、そのメソッドを持つオブジェクトがselfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せる」という原則が確認できます。
では反対に、「そのメソッドを持つオブジェクトがselfではないコンテキスト」とはどういう文脈のことだろうか。次のプログラムが、その最も簡単な例です。
require_relative 'samples_protected'
s1 = Sample1.new
s1.sample1
実際に上記のプログラムをtest_final.rbという名前で保存して実行すると、エラーメッセージが表示されます。エラーメッセージに、(NoMethodError) とあるのを確認しておいてください。
privateとprotectedの根本的な違い
privateとprotectedの違いについての結論を出す前に、privateに設定されたメソッドについて、もうひとうだけテストしておかなければならないことがあります。それは、instance_evalメソッドによる呼び出しについてです。
test3_protected.rbの1行目をrequire_relative ‘samples_private’に変更して、ファイル名をtest3_protected.rbとして、samples_private.rbと同じ階層に別名保存します。
これを実行すると、test1_private.rbの実行結果と同様に、1行目に”只今privateメソッドテスト中!”と表示され、2行目以降にエラーメッセージが表示されます。エラーメッセージ中には (NoMethodError) とあります。
これで、privateに設定されたメソッドをinstance_evalメソッドで呼び出すことは可能ですが、それは関数形式でならなければならないということが分かると思います。
そして、最後にtest_final.rbの1行目をrequire_relative ‘samples_private’に変更し、これを実行するとエラーメッセージが表示されます。エラーメッセージ中に (NoMethodError) とあるのが分かると思います。
つまり、privateに設定されたメソッドもprotectedに設定されたメソッドも、外部から直接呼びだすことはできないという点ではまったく同じものだと考えてよいようです。
ただし、呼び出しできない理由が微妙に違います。privateに設定されたメソッドの場合は、関数形式以外(つまりレシーバー形式)の呼び出しという理由から、protectedに設定されたメソッドの場合は、そのメソッドを持つオブジェクトがselfではないコンテキストだという理由から、それぞれ呼び出しが拒否されます。
では、どうやって両者を使い分けたらよいのでしょうか。リファレンスマニュアルに次のような例があります。
class Foo
def _val
@val
end
protected :_val
def op(other)
# otherもFooのインスタンスを想定
# _valがprivateだと関数形式でしか呼べないため
# このように利用できない
self._val + other._val
end
end
語感的は、privateよりもprotectedの方が制約が多いような気がしますが、上記の例に見るように、実際は、privateの方がprotectedより制約が多くなります。
本来は、外部からの呼び出し制限がprotectedの仕事で、privateはあくまでも関数定義のために作られたものだったのですが、いつの間には、外部からの呼び出し制限に、protectedよりprivateの方が使われることが多くなったというのが真実のようです。
そういえば、私も最初からprivate一本槍です。これはprivateという語感に関係しているのかもしれません。privateだと、このメソッドはあくまでも個人用だよということで、どことなく気軽に使えそうな気がするからです。
まあ、私のようにRuby on Rails等のRuby製のフレームワークを通じてRubyに触れた人にとっては、privateを使うかprotectedを使うかで悩む場面はほとんどないかもしれませんが、ときどきこうやってRubyそのものに触れて、いろいろと疑問を解決してみることも大切かもしれません。