概要
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
は実行不可で、これも自クラス・継承クラスに関わらない。