概要
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 | × | ○ | × |
※仲間=自クラスかサブクラスのレシーバー
参照
-
るりまの クラス/メソッドの定義・呼び出し制限 の説明
http://docs.ruby-lang.org/ja/2.1.0/doc/spec=2fdef.html#limit -
伊藤淳一さんのブログより
-
JavaやC#の常識が通用しないRubyのprivateメソッド
http://blog.jnito.com/entry/20120315/1331754912 -
Rubyのクラスメソッドは同じクラスのprotectedメソッドやprivateメソッドにアクセスできない
http://blog.jnito.com/entry/20120504/1336080083
-
-
Ruby のメーリングリストのやりとりより
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/7669 -
るりまの Module クラスの public / protected / private の説明