動機
先日なんとなくHatenaの匿名日記を読んでいたのですが、そこに「Javaでpublic/privateで内部のメンバ変数のアクセスをコントロールするのではなくsetter/getterを使うのはなぜか?」という疑問が書いてありました。私もなんとなく習慣的にgetter/setterを書く(あるいはlombokを使う)ことが多かったのですが、そう言えばなぜpublic/privateでメンバ変数へのアクセスをコントロールせず、メンバ変数を一律private変数にしてgetter/settterからアクセスする方式にしているんだろうと思いました。C#, PHPだとJavaほどきっちりgetter/setterを書かないような気がしました。
stackoverflowで調べてみた
検索してみたところこのページ(stackoverflow)がヒットしました。このページではgetter/setterを使う理由として以下が挙げられていました。
- メンバ変数へのアクセス(get/set)処理のカプセル化。メンバ変数のget/set時にget/set以外のことをしたい場合にも対応できるし、その処理をこのクラスを使用する側から隠蔽できる。
- getter/setter越しにアクセスする内部のメンバ変数を、このクラスを使用する側から隠蔽できるので、get〇〇という名前で実際は△△というメンバ変数を返すという処理ができるようになるし、3.のような変更も可能になる。
- このクラスを使用する側のコードを変えずにgetter/setter越しにアクセスされるメンバ変数を(後から)変更できる。
- そのメンバ変数のメモリ管理・ライフタイム管理(特にメモリ破棄の部分)の明確化。
- ランタイム時に内部のメンバ変数にアクセスするという処理に対してブレイクポイントを置けるようになるのでgetter/setterがあったほうがメンバ変数へのアクセス処理に対するデバッグがやりやすい。
- Mocking、Serializationなど、メンバ変数へのgetter/setterの存在を前提としている処理を外部ライブラリから呼び出す場合、素直にgetter/setterを使っていたほうが便利。
- メンバ変数へのgetter/setterが定義されたクラスを継承する子クラスを作る場合、子クラス側でメンバ変数へのアクセス処理(getter/setter関数)を変更することができる。
- ラムダ式でそのクラスから値を渡すのではなくgetter/setterを渡すということができる。
- getter/setter間で違うアクセスレベルが持てる。例えばgetterはpublicだけどsetterのみprotectedなど。
- プロパティのLazy load, Copy on writeができるようになる。
あとは以下のような回答もありました。
実装から2週間後(2か月後・2年後でもいいけど)、publicなメンバ変数で実装を済ませたけど後から実はメンバ変数への値のセットだけじゃ駄目で、値のセット時にそれ以上のことをする必要があることが判明したとする。いざその変更を実装しようと思ったら、そのメンバ変数が既に238個の外部クラスから参照されていたりすると、(どう修正するか)途方にくれることになるからね。
C#, PHPの場合
C#は自動プロパティ初期化機能があるのでgetter/setterをクラス内に書かなくてもgetter/setterを介したやりとりができるみたいですね。
PHPもIDEによっては自動でgetter/setter関数の作成ができるみたいですが、パフォーマンスの観点からはナイーブなsetter/getterは避けるべきということになっていたようです(stackoverflow)。ただこれが書かれたのがphp5当時っぽく、その後に出たphp7, php8は当時と比較してかなりパフォーマンスが向上しているはずなので現在はsetter/getterを使用したほうがいいのではないかと思いました。
stackoverflowでもPHPでsetter/getterを使うべき理由として以下のようなケースを挙げていました。setterを定義すれば(後からでも)バリデーション処理を追加できますね。
class Foo {
public $bar; // 本当はinteger型であることを期待している
}
$foo = new Foo;
$foo->bar = "string"; // 動的型付なのでstring型でもいれることができてしまう。
setter/getterはアンチパターン?
Readabilityの観点からは(ナイーブなgetter/setterは)望ましくないという意見もありました。lombokを使用したりしてgetter/setter関数で溢れるという事態は避けるのが良さそうです。
純粋なOOPではgetter/setterはアンチパターンだという主張をしている人もいました。ちゃんと読んではいないのですが(!!)、かわりにpublicなメンバ変数を使えと言っているのではなくそもそもオブジェクトをデータストラクチャとして使うな、OOPらしい関数を定義しろと言っている気がします。
結論
普段なんとなくでやっていることの意味を振り返るきっかけになってよかったです。