読者です 読者をやめる 読者になる 読者になる

フィールドごとに getter/setter を用意するな

未だにフィールドごとに getter/setter を用意しろって言う人がいてびっくりするわけですが。

フィールドごとに getter/setter を用意する人の言い分

  • そうやれって本/Webページに書いてある
  • フィールドに触れないと不便だろう
  • それが OOP

言い分に対する反論

そうやるなって本に書いてある

5章 オブジェクト指向エクササイズ
5.2.10 ルール9:Getter、Setter、プロパティを使用しないこと

ThoughtWorksアンソロジー
List<Book> getBooks() {
    return books;
}

これはクライアントに最大限の柔軟性を提供するが、さまざまな問題を生み出す. コレクションをそのまま返すと, その内容に依存している内部状態が知らない間に無効にされてしまうかもしれない.



代わりに, コレクション内の情報への, 限定された, 意味のあるアクセスを提供するメソッドを用意しよう.

void addBook(Book arrival) {
  books.add(arrival);
}
int bookCount() {
  return books.size();
}

クライアントがコレクションの要素を走査する必要があるのであれば, イテレータを返すメソッドを提供しよう

Iterator<Book> getBooks() {
  return books.iterator();
}
実装パターン

ロジックとデータを一箇所にまとめるという原則に従えば, パブリックまたはパッケージの可視性が getter メソッドに必要となることは, ロジックをほかのどこかに移動すべきだというあらわれである. getter メソッドを作成するよりも, そのデータを使用するロジックを移動してみよう.

実装パターン

setter メソッドを外から見えるようにすることは, getter メソッドよりもさらに私には抵抗がある. setter メソッドの名前は意図からではなく, 実装から付けられている.



setter メソッドによって, コードはもろくなる. 1つの原則は, 遠隔作用を避けることだ. オブジェクト A がオブジェクト B の内部表現の詳細に依存するとき, B のコードを変更すると, A のコードも変更しなければならなくなる. それは, A が本質的に変化したからではなく, ただ A が書かれた際の前提が変化したからだ.

実装パターン

アクセサしか持たないクラスは、オブジェクト指向の利点を真に活かしていない無意味なクラスであり、オブジェクト指向技術は単に浪費するだけの恐ろしいものになってしまいます。「フィールドのカプセル化」が終われば、次に、この新しいメソッドを利用するメソッドを探して、「メソッドの移動 (142)」を適用し、それらのメソッドが荷物をまとめて新しいオブジェクトに引越しできないかを調べます。

リファクタリング
フィールドに触れると不便

フィールドごとに getter/setter が用意されてるってことは、そのクラスがどんな内部状態を持っているかを暴露していることになる。
内部状態を隠蔽した方がいいと (多くの場合) その本に書いてあるのに、矛盾している。


また、フィールドごとに getter/setter があるとあまりに具体的過ぎて、「何を」やっているのかではなく、「どうやって」やっているのかしか表せない。
これは抽象化が足りていない証拠であり、悪い設計の兆候でもある。


内部に保持するオブジェクトを返す/受け取る場合は、そのオブジェクトがイミュータブルじゃない場合*1を除いては、防御的コピーを徹底すること。
いや、そもそも何らかの値をまとめるだけのクラスはイミュータブルにすべき。
それ以外のクラスは、極力内部に保持するオブジェクトを外部からアクセスできないようにすべき。

あなたの定義する OOP ってなんですか?

OOP の定義について、万人が認めるものが存在しない。
なので、「それ」を OOP と呼ぶのは勝手だけど、だからどうした。
ん?著名な誰某が言っていた?ちょっとは自分の頭で考えろ。


著名な誰某が言っていたからといって、それだけを見て OOP とするのはあまりにも根拠が薄すぎる。

*1:getter/setter 大好きな人に限ってクラスをイミュータブルにする利点を知らなかったりする