ひょんな拍子に、Rubyでprotected
なクラスメソッドを作る機会が生じましたが、ちょっとハマる箇所が出てきましたので、メモ程度に書き残しておきます。
Rubyのprotected
C++やJavaのprotected
と違って、Rubyのprotected
は、「self
がそのメソッドを定義したオブジェクトの場合」だけメソッドを呼べる、という意味合いです。@tbpgr さんのこちらの記事に詳しいです。
クラスメソッドをprotected
にすると
つまり、クラスメソッドをprotected
にすると、「クラスオブジェクト」自体がself
になる場面、すなわちそのクラスやサブクラスのクラスメソッド内、あるいは特異クラスの中からしかアクセスできなくなります。呼べる箇所はかなり限定的です。
どんなときに使う?
なかなか使う機会がないようにも思えますが、new
をprotected
にしてみると、外部からは別のクラスメソッドでインスタンスを取るしかない、というクラスを作れます。もちろんそれだけであればprivate
でもいいのですが、継承関係などを考えているとprotected
でうまくいく、という例がありました(ただし、これでせいとうなのかはちょっと判断がついていません)。
実際にしてみる方法
Rubyでメソッドのアクセス権を変える方法としては、大分すれば2つあります。
-
public
、private
、protected
を引数なしで使って、状況を切り替えたうえでメソッドを定義する - これらのメソッドにシンボルを渡して、そのメソッドだけ可視性を切り替える
クラスメソッド用に、private_class_method
やpublic_class_method
が用意してありますが、需要がないからかprotected_class_method
は用意してありません。そして、new
などは親クラスの側で用意してあるので、protected
な雰囲気の中で再定義するわけにも行きません。ということで、class << self; protected :new; end
というように特異クラスをオープンすることになりました。
疑問~子クラスのが呼べるけど、どうなの?
ただ、これを書くために改めて調べていたのですが、やはり疑問が出てきました。抽象的な親クラスにいくつかの子クラスがある状況で、親クラスでprotected
したとはいえ、子クラスのクラスメソッドを親クラスのクラスメソッド内から呼べてしまっているのです。これは意図した動作なのか、はたまたたまたま動いているだけなのかわからず、気持ち悪い状態に陥っています。
class AbstractParent
class << self; protected :new; end
def self.some_method
# 実際には状況によって子クラスは入れ替わる
klass = Child1
obj = klass.new # エラーにならない
# 後略
end
end
class Child1 < AbstractParent; end
class Child2 < AbstractParent; end