🦔

なんでそうなってんの?から理解する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