スポンサーリンク

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なインスタンスメソッドを呼べる。

補足

言うまでもなく,プライベートなインスタンスメソッドを呼び出す方法として

    hoge.__send__("some_private_instance_method")

とか

    hoge.instance_eval do
      some_private_instance_method
    end

は今回取り上げていない。