【Ruby v2.6】protected とクラスメソッド、インスタンスメソッドの関係 ※2020.8.25 追記
前置き
C# や JAVA でのメソッドの protected
指定は、「そのクラスと継承したクラスからアクセス可能」という意味ですが、Ruby の場合はちょっと挙動が異なるみたいなので、まとめてみました。
protected
なクラスメソッド、protected
なインスタンスメソッドの関係
結論としては、以下の2点です。
protected
なクラスメソッドは、インスタンスメソッドからは、同じクラスからも継承関係にあるクラスからも呼び出せない。protected
なインスタンスメソッドは、クラスメソッドからは、同じクラスからも継承関係にあるクラスからも呼び出せない。
C# や JAVA の場合、クラスメソッド、インスタンスメソッドの括りなんか関係なく、同じクラス、継承関係にあるクラスのメソッドから呼び出せるのですが、Ruby では、インスタンスメソッド、クラスメソッドの間では protected
はアクセスできないのです。
例えば・・・
ぱっと思いつく例としては、
- クラス A のクラスメソッドから、各インスタンスのインスタンスメソッドにアクセスして、持っている値を収集したい。でも別の関係ないクラスから値を参照されても困るから、インスタンスメソッドを
protected
にしておこう。。。
とか、逆に・・・
- クラス A の各インスタンスから、処理した内容をクラス A の変数に集約するために、クラス A に情報集約用のクラスメソッドを作ろう。でも、クラス A のインスタンス以外からアクセスされたら困るから、クラスメソッドを
protected
にしておこう。。。
ということができないのです。この場合、public のままで妥協するか、別の手段を考える必要があります。
なんでこんな仕様なのか、自分なりに考えてみた。
最初は、すごく不思議な仕様な感じがしましたが、インスタンスメソッド、クラスメソッドの格納先を分けて考えてみると、少しだけ「なるほど」と思えるようになりました。
Ruby では、インスタンスメソッド、クラスメソッドの定義は同じ場所に書きますが、実体としては、インスタンスメソッドはクラスに、クラスメソッドは特異クラスに格納されます。ですので、あえて図でそのように分けて書いてみると、下記のようになります。
この図で、左が「クラス A」、右が「クラス A の特異クラス」です。「クラス A」で定義しているのがインスタンスメソッド、「クラス A の特異クラス」で定義しているのがクラスメソッドになります。
- クラス A のインスタンスメソッド
public_ins_method
から、protected なインスタンスメソッドはアクセス OK、protected なクラスメソッドはアクセス NG です。 - クラス A のクラスメソッド
public_class_method
から、protected なインスタンスメソッドはアクセス NG、protected なクラスメソッドはアクセス OK です。
図で見ると一目瞭然ですが、同じクラス内に格納されていればアクセスOK、そうでなければアクセス NG ということが分かります。
さらに、クラス A を継承したクラス B をつけてみます。
クラス A とクラス B 、クラス A の特異クラスとクラス B の特異クラス は、それぞれ継承関係にあります。 継承関係にあるクラス間であればアクセス OK ですが、やはりタスキがけのアクセスは NG となっています。
このことから。
protected
は、同じクラス、もしくは継承関係にあるクラスのメソッドからアクセスが可能、というのは正しい。- ただ、Ruby の場合は、インスタンスメソッドとクラスメソッドの格納先が異なっている。
- インスタンスメソッドとクラスメソッドの間では「実行しているクラスが異なり」、「クラスと特異クラスは継承関係にない」ので、
protected
ではアクセス NG となる。
ということなのかなー、と勝手に理解してみました。
※ あえてかぎかっこ「」で括った文言は、深くツッコみだすとはまりそうな気がするので、これ以上はツッコまないことにします。
追記(2020.8.25)
「継承関係にないとアクセスできない」というところで、ふと、思い付きがありました。
以前の記事で書いたのですが、特異クラスの親を追っていくと、BasicObjectの特異クラスから Classクラスに回って、最終的にBasicObject にたどりきます。 図で書いたのを再掲しますと。
クラス A を普通に生成した場合、Objectクラスからの派生になるので、クラス A の特異クラスの継承ルートにクラス A は入ってきません。
じゃあ、Objectクラスだったら?Object クラスの特異クラスの場合、継承ルートにObject クラスが入ってきます。
ということは、Objectクラスのクラスメソッドだったら、Objectクラスの protected インスタンスメソッドにアクセスできる?
というわけで、試してみました。 比較のために、Object クラス と クラス A に、インスタンス生成のところだけを変えた同じメソッドを追加してみました。
class Object def self.class_method_test obj = Object.new # <= 違いはここだけ obj.protected_method_test end protected def protected_method_test "called protected_method_test" end end class A def self.class_method_test obj = A.new # <= 違いはここだけ obj.protected_method_test end protected def protected_method_test "called protected_method_test" end end
結果は、こうなりました。
A.class_method_test # => NoMethodError (protected method `protected_method_test' called for #<A:0x0000000005812310>) Object.class_method_test # => "called protected_method_test"
Object クラスの方は、protected なインスタンスメソッドにアクセスできました。やっぱり、継承関係か否か、で見てるっぽいですね。なので、Object クラスやClassクラス、Moduleクラスの Protected インスタンスメソッドなら、同じクラスのクラスメソッドからアクセス可能です。
・ ・ ・
でもこんなことわかっても、役に立ちそうなシーンが思い浮かばないですね・・・ そこまでして protected を使う意味がなさそうです。