Ruby

Ruby の private と protected 。歴史と使い分け

More than 1 year has passed since last update.

概要

Ruby の private と protected の歴史と使い分けについて説明します。

いろんなところで断片的に書かれていることなのですが、有益な情報を
一箇所に集めると自分の理解が深まりそう=>他の人にも役立つかな?
と思ったのでまとめてみました。

具体的には、伊藤淳一さんのブログ・るりま・Rubyメーリングリストの内容を
一箇所にまとめた上で、私が書いたサンプルコードを少し足した内容になっています。

ちなみに Java や C# 畑の人が Ruby の private / protected を使って驚いた、
的な情報が多いですが、私も例にもれず Java => Ruby 勢で驚いたパターンです。

Java の private と protected

  • public => どこからでもアクセス可能
  • protected => クラス内、同一パッケージ、サブクラスからアクセス可
  • private => クラス内のみアクセス可能

制限無く公開したいなら public 。
基本的に外部からは隠蔽するが、サブクラスやパッケージ内からのみ利用させたい場合は protected 。
基本的に外部からは隠蔽し、クラス内からのみ利用させたい場合は private 。

という使い分けになっていて、かなり分かりやすく、使い分ける目的も明確です。

今回の話には関係ありませんが、アクセス修飾子を省略した場合には default というスコープもあります。

  • default => クラス内、同一パッケージからアクセス可

Ruby の private と protected

るりまを確認

るりまでは下記のように説明されています。
http://docs.ruby-lang.org/ja/2.1.0/doc/spec=2fdef.html#limit

  • public => public に設定されたメソッドは制限なしに呼び出せます。
  • protected => protected に設定されたメソッドは、そのメソッドを持つオブジェクトが self であるコンテキスト(メソッド定義式や instance_eval )でのみ呼び出せ ます。
  • private => private に設定されたメソッドは関数形式でしか呼び出せません。

※便宜上、るりまとは表記順を変えてあります。(スコープの範囲が広い順にした方が分かりやすいと思ったので)

説明だけだとピンと来ないかもしれないので、サンプルコードを提示します。

private と protected の動作を確認

require 'english'

class ScopeResearchClass
  def public_method
    'public'
  end

  def use_protected(other)
    puts other.protected_method
  end

  def use_private(other)
    other.private_method
  rescue
    puts $ERROR_INFO
  end

  def internal_use_private_and_protected
    puts protected_method
    puts private_method
  end

  def internal_use_private_and_protected_with_reciever
    puts self.protected_method
    self.private_method
  rescue
    puts $ERROR_INFO
  end

  protected

  def protected_method
    'protected'
  end

  private

  def private_method
    'private'
  end
end

pc1 = ScopeResearchClass.new
pc2 = ScopeResearchClass.new

begin
  pc1.protected_method
rescue
  # protected は外部からは呼び出せずにエラー
  puts $ERROR_INFO
end

begin
  pc1.private_method
rescue
  # private は外部からは呼び出せずにエラー
  puts $ERROR_INFO
end

# private / protected ともに内部から利用可能
pc1.internal_use_private_and_protected

# protected は レシーバーつきでも呼び出し可能
# private は レシーバーつきだと呼び出せず
pc1.internal_use_private_and_protected_with_reciever

class Hoge
  def can_not_use_external_protected_method(other)
    other.protected_method
  rescue
    puts $ERROR_INFO
  end
end

# 関係ないクラス内からは呼び出せないことを確認
Hoge.new.can_not_use_external_protected_method(pc2)

# protected メソッドは自クラスに別インスタンスを渡しても呼び出し可能
pc1.use_protected(pc2)

# private メソッドは自クラスに別インスタンスを渡した場合、レシーバーの指定が出来ないのでエラーになる
pc1.use_private(pc2)

class ChildProtectedClass < ScopeResearchClass
  def use_protected_from_child(other)
    puts other.protected_method
  end
end

# protected メソッドはサブクラスに別インスタンスを渡しても呼び出し可能
ChildProtectedClass.new.use_protected_from_child(pc2)
  • 出力
protected method `protected_method' called for #<ScopeResearchClass:0x0000060044c770>
private method `private_method' called for #<ScopeResearchClass:0x0000060044c770>
protected
private
protected
private method `private_method' called for #<ScopeResearchClass:0x0000060044c770>
protected method `protected_method' called for #<ScopeResearchClass:0x0000060044c748>
protected
private method `private_method' called for #<ScopeResearchClass:0x0000060044c748>
protected

protected を使うべき状況

protected を使うべき状況について、るりまにサンプルコードが掲載されていますが、
サンプルコードに少し補足をして、実行して動きを確認できるプログラムにしてみます。

class Foo
  def _val
    @val
  end
  protected :_val

  def op(other)

    # other も Foo のインスタンスを想定
    # _val が private だと関数形式でしか呼べないため
    # このように利用できない

    self._val + other._val
  end
end

f1 = Foo.new
f1.instance_variable_set(:@val, 1) # => @val に無理やり 1 を設定
f2 = Foo.new
f2.instance_variable_set(:@val, 2) # => @val に無理やり 2 を設定
puts f1.op(f2) # => 3

Ruby の private と protected の歴史

伊藤淳一さんの素晴らしいブログエントリに Ruby のパパ「 Matz 」さんとのやりとりが
まとまっています。
http://blog.jnito.com/entry/20120315/1331754912

  • Matz さん曰く
Rubyのprivateの発想の元になったのはSmalltalkの「privateカテゴリ」です。
使わないでね、というだけでアクセスできちゃう。
Rubyはそれよりは若干強制力があります。
Rubyの反C++・親Smalltalkの設計思想が垣間見えますね

後でprotectedを追加したのもまずかった。
これでC++とキーワードが同じでも意味がズレてることになってしまったので。

private と protected の使い分けに関する Matz さんの分かりやすい説明

Rubyのメーリングリストのやりとりより。
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/7669

  • Matz さん曰く
つまり,privateは自分からしか見えないメソッドであるのに対し
て,protectedは一般の人からは見られたくないが,仲間(クラスが
同じオブジェクト)からは見えるメソッドです.

protectedは例えば2項演算子の実装にもう一方のオブジェクトの状
態を知る必要があるか調べる必要があるが,そのメソッドをpublic
にして,広く公開するのは避けたいというような時に使います.

Ruby のメソッドの公開範囲の決定に関するマトリクス

スコープ 全体に公開したい 外部から隠蔽したい レシーバーを仲間が利用する
public ×
protected ×
private × ×

※仲間=自クラスかサブクラスのレシーバー

参照