クラスメソッドの定義
Rubyでクラスメソッド(=クラスオブジェクトの特異メソッド)を定義するには、いくつかの方法があります。
たとえば、Hoge クラスオブジェクトに hoge() メソッドを定義する場合、
1. クラス定義内でクラスオブジェクトに特異メソッドを定義
class Hoge def self.hoge() end end
2. クラス定義内で特異クラスに対してメソッドを定義
class Hoge class << self def hoge() end end end
3. クラス定義外でクラスオブジェクトに特異メソッドを定義
class Hoge end def Hoge.hoge() end
4. クラス定義外で特異クラスに対してメソッドを定義
class Hoge end class << Hoge def hoge() end end
いずれも外部から Hoge.hoge() としてメソッドを呼ぶことができますが、個人的には 1 の方法が好みです。
2 は def の行だけ見たときにクラスメソッドなのかインスタンスメソッドなのかわからないし、インデントが1段階深くなってしまいます。
3 だと class Hoge 内で定義した定数が定数名だけで参照できません。
class Hoge Fuga = 123 end def Hoge.hoge() Hoge::Fuga #=> 123 Fuga # uninitialized constant Fuga (NameError) end
定数の名前解決はクラスやメソッド階層ではなく、定数を参照した箇所からみたスコープに依存するためのようです。詳しくはしりません。
4 はさらにうっかり次のように書くと同じ名前で別の定数が定義できたりします。
class Hoge Fuga = 123 end class << Hoge Fuga = 456 def hoge() Fuga end end
「class << Hoge」内のコンテキストは Hoge ではなくて、Hoge の特異クラスだからみたいです。詳しくはしりません。
クラスメソッドの private 化
クラスメソッドもインスタンスメソッドと同様に private 化することができます。
class Hoge def self.hoge() end def self.fuga() end private_class_method :fuga end Hoge.hoge # OK Hoge.fuga # private method `fuga' called for Hoge:Class (NoMethodError)
インスタンスメソッドの private とは異なり、引数なしで指定してもそれ以降が private になるわけではありません。
class Hoge def self.hoge() end private_class_method # 単に無視される def self.fuga() end end Hoge.hoge # OK Hoge.fuga # OK
次のように特異クラスを使用すれば可能です。
class Hoge class << self def hoge() end private def fuga() end end end Hoge.hoge # OK Hoge.fuga # private method `fuga' called for Hoge:Class (NoMethodError)
ですが、上述の理由により特異クラスでクラスメソッドの定義はあまりしたくありません。
ということで、引数なしで private_class_method が指定された時に、それ以降で定義されたクラスメソッドが private になるような仕組みを考えてみました。
module PrivateClassMethod def private_class_method(*args) # 引数が指定されない時は、 if args.empty? # 特異メソッドが追加された時に、 def self.singleton_method_added(name) # 特異メソッドを private に指定 private_class_method name end # クラスが継承された場合、 def self.inherited(subclass) # 特異メソッドを private にする機能を無効化 subclass.class_eval do def self.singleton_method_added(name) end end end else # 引数が空でない時は元の動き super end end end class Hoge # PrivateClassMethod の機能をクラスに取り込む extend PrivateClassMethod def self.hoge() end private_class_method def self.fuga() end end Hoge.hoge # OK Hoge.fuga # private method `fuga' called for Hoge:Class (NoMethodError)
Ruby 本来の動きとは異なりますが、個人的には便利だと思います。