なぜRubyのattr_accessor、attr_reader、attr_writerを使うのですか?

Rubyでは、以下のようなキーを使ってインスタンス変数を共有する便利な方法があります。

attr_accessor :var
attr_reader :var
attr_writer :var

単純に attr_accessor を使えるのに、なぜ attr_readerattr_writer を選ぶのでしょうか?パフォーマンスのようなものがあるのでしょうか(疑わしいですが)。何か理由があるのでしょう、そうでなければこのようなキーは作られません。

ソリューション

アクセサを使い分けることで、コードを読む人に意図を伝えたり、パブリックAPIがどのように呼び出されても正しく動作するクラスを簡単に書くことができます。

class Person
  attr_accessor :age
  ...
end

ここでは、年齢の読み取りと書き込みの両方が可能であることがわかります。

class Person
  attr_reader :age
  ...
end

ここでは、年齢の読み取りのみ可能であることがわかります。 年齢は、このクラスのコンストラクタで設定され、その後は一定であると想像してください。 ageのミューテーター(ライター)があって、一度設定されたageは変化しないという前提でクラスが書かれていた場合、そのミューテーターを呼び出すコードからバグが発生する可能性があります。

しかし、舞台裏では何が起こっているのでしょうか?

と書くと

attr_writer :age

と翻訳されてしまいます。

def age=(value)
  @age = value
end

と書けば。

attr_reader :age

と翻訳されてしまいます。

def age
  @age
end

と書けば。

attr_accessor :age

と翻訳されてしまいます。

def age=(value)
  @age = value
end

def age
  @age
end

これを知った上で、別の方法で考えてみましょう。attr_...ヘルパーがなく、自分でアクセサを書かなければならないとしたら、自分のクラスが必要とする以上のアクセサを書きますか? 例えば、ageが読まれるだけでいいなら、それを書けるようにするメソッドも書きますか?

解説 (10)

オブジェクトのすべての属性が、クラスの外から直接設定できるわけではありません。すべてのインスタンス変数にライターを持つことは、一般的にカプセル化が弱いことを示しており、クラス間の結合が強すぎることを警告しています。

実例を挙げてみましょう。私は、コンテナの中にアイテムを入れるデザインプログラムを書きました。アイテムは attr_reader :container を持っていましたが、ライターを提供する意味はありませんでした。アイテムのコンテナが変わるのは、新しいコンテナに入れるときだけで、そのときには位置情報も必要になるからです。

解説 (0)

インスタンス変数をクラスの外から完全にアクセスできるようにしたいとは限りません。インスタンス変数への読み取りアクセスを許可することは意味がありますが、書き込みを許可することは意味がない場合がたくさんあります(例えば、読み取り専用のソースからデータを取得するモデルなど)。逆のことをしたい場合もあるでしょうが、私の頭の中では工夫していないものは思いつきません。

解説 (0)