Rubyの動かないコード (初級編) 同じクラス内なのに,privateメソッドを呼べない場合がある
以下のRubyのコードが,意図した動作をしないのはなぜですか。(制限時間1分)
やりたい事:
- 1つのクラス内で定義されているメソッドを,順番に呼び出して実行する。
hoge.rb
# クラス定義 class Hoge def self.main_method # このクラス中で定義されているクラスメソッドの呼び出し some_public_class_method some_private_class_method # このクラス中で定義されているインスタンスメソッドの呼び出し hoge = self.new # インスタンス作成 hoge.some_public_instance_method hoge.some_private_instance_method end def self.some_public_class_method p "パブリックなクラスメソッドです。" end def some_public_instance_method p "パブリックなインスタンスメソッドです。" end private_class_method def self.some_private_class_method p "プライベートなクラスメソッドです。" end private def some_private_instance_method p "プライベートなインスタンスメソッドです。" end end # 全てを実行 Hoge.main_method
発生する問題
publicメソッドは2つとも正しく定義できており,問題は起きない。
privateなクラスメソッドもOK。
Rubyでクラスメソッドをprivateにする正しい方法
http://blog.s21g.com/articles/561
- privateでプライベートなインスタンスメソッド
- private_class_methodでプライベートなクラスメソッド
※ただし,private_class_methodではなく特異クラスを使うほうが一般的
privateなインスタンスメソッドの呼び出しでエラーになる。
ruby -Ks hoge.rb "パブリックなクラスメソッドです。" "プライベートなクラスメソッドです。" "パブリックなインスタンスメソッドです。" hoge.rb:14:in `main_method': private method `some_private_instance_method' calle d for #<Hoge:0x287fd78> (NoMethodError) from hoge.rb:47
原因
Rubyでは,「private」の意味がJavaと異なる。
5.7 メソッドの可視性を指定したいのですが
http://www.ruby-lang.org/ja/man/html/...
- Rubyでは関数形式(レシーバを省略した形)でしか呼び出すことのできないメソッドのことをprivateなメソッドと呼んでいます。C++やJavaのprivateとは意味が違うので注意してください。
つまり,
some_private_instance_method
というコードは,メソッドの前にレシーバがないので書き得る。(Hogeのインスタンスの中に限られるが)。
しかし
hoge.some_private_instance_method
というコードは,メソッドの前にレシーバが付いているので,たとえHogeクラス内であっても書けないのだ。
Rubyでの事情をまとめると,こうなる。
- クラスメソッド内では,privateなクラスメソッドを呼べる。
- クラスメソッド内では,privateなインスタンスメソッドを呼べない。
- 同一クラスだが,同一オブジェクトではないので。
なお,Javaでは事情が異なる。
public class Hoge { public static void main( String[] args ) { // このクラス中で定義されているクラスメソッドの呼び出し some_public_class_method(); some_private_class_method(); // このクラス中で定義されているインスタンスメソッドの呼び出し Hoge hoge = new Hoge(); hoge.some_public_instance_method(); hoge.some_private_instance_method(); } public static void some_public_class_method() { System.out.println("public class method"); } public void some_public_instance_method() { System.out.println("public instance method"); } private static void some_private_class_method() { System.out.println("private class method"); } private void some_private_instance_method() { System.out.println("private instance method"); } }
このJavaのコードはちゃんと動く。(クラス設計の善しあしは別として)
解決策としては,Rubyの場合は,クラス設計をやり直す事になる。
関連
protectedについても触れておく。
- インスタンスメソッド内では,同一インスタンスのprivateなインスタンスメソッドを呼べる。
- インスタンスメソッド内では,同一クラスの他のインスタンスのprivateなインスタンスメソッドを呼べない。
- インスタンスメソッド内では,同一クラスの他のインスタンスのprotectedなインスタンスメソッドを呼べる。