概要
Rubyにおけるprivateとprotectedは、C++やJavaでの振る舞いと一部で異なる。慣れている言語との違いというよりも、Rubyの場合言葉の概念から予想される挙動がシンプルに想起できない。
自クラス内での挙動
以下のコードで自クラス内での挙動を確認する。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class ParentClass   def public_method     puts "public"   end   private   def private_method     puts "private"   end   protected   def protected_method     puts "protected"   end   public   def internal_public_call_as_function     public_method   end   def internal_private_call_as_function     private_method   end   def internal_protected_call_as_function     protected_method   end   def internal_public_call_from_receiver     self.public_method   end   def internal_private_call_from_receiver     self.private_method   end   def internal_protected_call_from_receiver     self.protected_method   end end | 
直接呼出し
まず次のメソッドに着目する。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class ParentClass class ParentClass   def public_method     puts "public"   end   private   def private_method     puts "private"   end   protected   def protected_method     puts "protected"   end ..... end | 
これらのメソッドを、インスタンスから直接呼び出した結果。publicメソッドは呼び出せるが、private/protectedメソッドは隠蔽されている。これは想定通り。
| 1 2 3 4 5 6 7 8 9 10 11 | p1 = ParentClass.new p1.public_method # public p1.private_method # ERROR # in `<main>': private method `private_method' called for #<ParentClass:0x00000000050d48a0> (NoMethodError) p1.protected_method # ERROR # in `<main>': protected method `protected_method' called for #<ParentClass:0x00000000051b05f8> (NoMethodError) | 
関数アクセス
次に、同一クラスの中でこれら3つのメソッドに関数としてアクセスするメソッドの挙動を見てみる。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Class ParentClass .....   public   def internal_public_call_as_function     public_method   end   def internal_private_call_as_function     private_method   end   def internal_protected_call_as_function     protected_method   end ..... end | 
これらのメソッドをインスタンスから呼び出した結果はすべてエラー無く、同一クラス内からは全て利用可能なことがわかる。
| 1 2 3 4 5 6 7 8 | p1.internal_public_call_as_function # public p1.internal_private_call_as_function # private p1.internal_protected_call_as_function # protected | 
レシーバーアクセス
さらに、関数としてのアクセスではなく、レシーバーのメソッドとしてアクセスするメソッドを試してみる。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class ParentClass .....   def internal_public_call_from_receiver(other)     other.public_method   end   def internal_private_call_from_receiver(other)     other.private_method   end   def internal_protected_call_from_receiver(other)     other.protected_method   end end | 
同じParentClassの新たなインスタンスを生成し、これを渡してみると、privateメソッドのみエラーとなる。
| 1 2 3 4 5 6 7 8 9 10 | p2 = ParentClass.new p1.internal_public_call_from_receiver(p2) # public p1.internal_private_call_from_receiver(p2) # ERROR # in `internal_private_call_from_receiver': private method `private_method' called for #<ParentClass:0x0000000005045cb8> (NoMethodError) p1.internal_protected_call_from_receiver(p2) # protected | 
継承クラスでの挙動
ParentClassの継承クラスChildClassを定義し親クラスのメソッドを呼び出すと、上と同じ結果になり、privateとprotectedは隠蔽されている。
| 1 2 3 4 5 6 7 8 9 10 11 | c1 = ChildClass.new c1.public_method # public c1.private_method # ERROR # in `<main>': private method `private_method' called for #<ChildClass:0x0000000005164310> (NoMethodError) c1.protected_method # ERROR # in `<main>': protected method `protected_method' called for #<ChildClass:0x0000000005071930> (NoMethodError) | 
関数呼び出しの場合、親クラスのメソッドに全てアクセス可能で、これも自クラス呼び出しと同じ。
| 1 2 3 4 5 6 7 8 | c1.internal_public_call_as_function # public c1.internal_private_call_as_function # private c1.internal_protected_call_as_function # protected | 
別のChildClassのインスタンスを生成し、これをレシーバーとしてメソッドを呼び出すと、privateのみエラーとなる。
| 1 2 3 4 5 6 7 8 9 10 | c2 = ChildClass.new c1.internal_public_call_from_receiver(c2) # public c1.internal_private_call_from_receiver(c2) # ERROR # in `internal_private_call_from_receiver': private method `private_method' called for #<ChildClass:0x00000000050917a8> (NoMethodError) c1.internal_protected_call_from_receiver(c2) # protected | 
なお、関数呼び出し、レシーバー呼び出しの各internalメソッドをChildClass内でオーバーライドしても結果は同じになる。
まとめ
これらをまとめると次のようになる。
| public | protected | private | |
| 自クラス 直接コール | ○ | × | × | 
| 自クラス 関数コール | ○ | ○ | ○ | 
| 自クラス レシーバー | ○ | ○ | × | 
| 継承クラス 直接コール | ○ | × | × | 
| 継承クラス 関数コール | ○ | ○ | ○ | 
| 継承クラス レシーバー | ○ | ○ | × | 
すなわち、publicは想定通り、関数コールの場合protectedもprivateも自クラス・継承クラスに関わらず実行可能。レシーバー呼び出しの場合protectedは実行可能だがprivateは実行不可で、これも自クラス・継承クラスに関わらない。