なんでそうなってんの?から理解するRubyのprivate / protected
はじめに
Rubyを使っている方の多くは、privateとprotectedの仕様を理解していると思います。
しかし、そのような仕様になっている理由を把握していない方もいるのではないでしょうか。
本記事では、Rubyの private/protected がなぜこのような挙動をするのかを、設計思想の観点から整理します。
前提
前提として、privateとprotectedの仕様をまとめておきます。
Rubyやプログラミングの初心者でなければ、多くの方は理解していることだと思います。
| 観点 | private |
protected |
|---|---|---|
| アクセス範囲 | 自クラスおよびサブクラス | 自クラスおよびサブクラス |
| 呼び出し方 | レシーバを付けずに呼び出す(レシーバを付けることはできるがselfに限定される) | レシーバを付けて呼び出す |
| 他インスタンスからの呼び出し | 不可 | 可能 |
なぜprivateメソッドではレシーバを指定出来ないのか?
レシーバを指定しないことで、オブジェクトに対するメッセージ送信ではないことを明示でき、その結果privateメソッドが内部実装である意図を表現できる、というのが結論となります。
実例
privateメソッドは、レシーバを明示して呼び出すことができません。
class User
def test
secret
self.secret # selfは指定可能。昔はselfも付けられなかった。
end
private
def secret
puts "This is a secret method."
end
end
公式ドキュメントには以下のように書かれています。
private に設定されたメソッドは関数形式でしか呼び出せません。
関数形式でしか呼び出せないことにより、privateは「そのオブジェクト自身の内部実装である」ことを明示できます。
内部実装って何やねん
以下が一例になります。
normalize_nameでは、userオブジェクトのnameのみを使われています。
つまり、そのオブジェクト自身の状態のみを用いて完結する処理です。
class User
def display_name
normalize_name
end
private
def normalize_name
# これが内部処理
@name.strip.capitalize
end
end
「オブジェクトにメッセージを送る処理ではない」ことを表現するために、privateメソッドではレシーバを指定出来ないのです。
なぜprotectedメソッドではレシーバを指定できるのか?
protectedは「オブジェクト間のメッセージ送信」とする意図があるためです。
実例
protectedメソッドは、レシーバを明示して呼び出します。
ageの暗黙のselfとother_userがレシーバです。
class User
def older_than?(other_user)
age > other_user.age
end
protected
def age
@age
end
end
公式ドキュメントには以下のように書かれています。
protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。
つまり、隠蔽はしたいけどオブジェクト間でゴニョゴニョしたいな...という時はprotectedを使うわけです。privateを使うとレシーバを指定できないので、オブジェクトを識別出来ません。
まとめ
Rubyのprivate/protectedは「アクセス制限」という意味は薄いです。
privateメソッドがサブクラスから呼び出せるので、他の言語の経験者はびっくりするらしいですね。
メソッドの責務を表現するための機能という言い方の方が近いでしょうか。
| 修飾子 | 意図 |
|---|---|
| public | 外部に公開するAPI |
| private | 自分自身の内部実装 |
| protected | 同一ドメインオブジェクト間の協調 |
private や protected の仕様は、ただ覚えるだけでも何となく使えるかもしれません。
しかし、その背景を理解すると、
- 設計の意図がコードから読み取れる
- 責務を分離できる
というメリットが享受できてとってもハッピーです。
Discussion