Rubyの呼び出し制限privateとprotectedの違い

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
  

実行結果は次のとおり。

private_method_test01

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
  

実行結果は、次のとおり。

private_method_test02

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
  

実行結果は次のとおり。

private_method_test03

これにより、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
  

実行結果は次のとおり。

private_method_test04

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そのものに触れて、いろいろと疑問を解決してみることも大切かもしれない。