はじめに
オブジェクト指向について勉強すると、「カプセル化」という単語に必ず出会うと思いますが、なぜかその意味を間違って理解している人がいます。
「カプセル化」自体はそれほど難しい話ではないですが、これを知ってるかどうかでコードの品質が変わってくると考えているので、本記事でまとめたいと思います。
よくある誤った「カプセル化」の理解
よくある誤った理解は以下です。
「クラスのフィールドをprivateで定義し、getter、setterを使用してprivateなフィールドにアクセスしたり、値を変更すること」
このような誤った理解をする人がいるのは、情報が不足しすぎている記事や、誤った理解を伝えている人がいることが原因なのかなと思っています。
じゃあ、カプセル化って何なの?
カプセル化とは、クラスがもつデータ(フィールド)と(メソッド内の)処理を外部から隠蔽することです。
例えば、「りんご」というクラスがあったとして、そのクラスに「糖度を計算する」というメソッドがあるとします。
外部からはこのメソッドを使用して、糖度を取得することはできますが、何の値を使ってどのように糖度を計算したかは隠蔽されます。
このようにすることで、各クラスの責務を明確化し、独立性を高めることができます。
カプセル化を行わないと、どこにどの実装があって影響範囲がどこまであるのかが不明瞭になり、可読性が低くバグの生じやすいコードになってしまいます。
getter、setterは極力使わない
上記のカプセル化を踏まえて、「外部から隠蔽することはわかった。じゃあgetter、setterってなに?」と思った人もいると思います。
結論、getterは極力使うべきでなく、setterは使うべきでないです。
理由は簡単で、これらを使用することでクラス内のロジックが外部に漏れ始めます。隠蔽して独立性を高めることに反しますね。
ただ、getterに関しては、実装が必要な時もあると思います。その時は、getterで取得した値でクラス内で行うべき処理を実装しないように気をつけましょう。
setterは完全な悪手で、そもそもオブジェクトは不変(immutable)であるべきです。
「じゃあ、フィールドの値を更新する必要があるときはどうするんだ!」という人がいるかもしれませんが、その場合は新しいオブジェクトを生成するように設計しましょう。
Personクラスのnameフィールドを変更する際の例を以下に示します。
public final Person {
String name;
LocalDate birthday;
Person(String name, LocalDate birthday) {
this.name = name;
this.birthday = birthday;
}
public Person updateName(String name) {
return Person(name, birthday);
}
}
おわりに
オブジェクト指向を学ぶ上でも、少しでも良いコードを書く上でもカプセル化は大切なので、少しでも誤解する人が減ると嬉しいなと思います!