値オブジェクトとコレクションオブジェクト
某先輩の書評を読んで気になったので 『現場で役立つシステム設計の原則』 という本を読んだ。
とあるプロジェクトでその先輩に自分の書くコードについて色々ご指摘をいただいていたタイミングであり、自分としてはかなり参考になった点があるので備忘も兼ねて書いてみる。
今回は特に「値オブジェクト」と「コレクションオブジェクト(ファーストクラスコレクション)」について。
値オブジェクト
値オブジェクトはアプリケーションに登場する様々な値に対してその値を扱うための専用クラスを作るという考え方。
値ごとにクラスを定義する
値オブジェクトの考え方をルール化すると、「プリミティブ型や String
型は使わない」という方針となる。
例えば、先述のプロジェクトのコードでは登場する様々な値を String
型で表現していた。
userId
nickname
address
- etc...
これらはサーバーリクエストの口でバリデーションを通るものの、その後の値がアプリケーションコード内で保証されないため非常に不安定だった。
また、例えば nickname
と address
を引数として受け付けるメソッドがあったとすると
public User hoge(String nickname, String address) {
...
}
となるわけだが、この時このメソッドを使う側で間違えて address
, nickname
の順で引数を指定したとしてもこのメソッドはその引数を受け付けてしまう。
そこで Nickname
クラスと Address
クラスを定義するというのが値オブジェクトの考え方だ。
class Nickname {
static final int MIN_LENGTH = 1;
static final int MAX_LENGTH = 20;
String nickname;
Nickname(String nickname) {
if (nickname.length() < MIN_LENGTH) {
throw new IllegalArgumentException("nickname length must be at least" + MIN_LENGTH);
}
if (nickname.length() > MAX_LENGTH) {
throw new IllegalArgumentException("nickname length must be under" + MAX_LENGTH);
}
this.nickname = nickname;
}
}
address
に対しても同様にクラスを定義して先ほどのメソッドを
public User hoge(Nickname nickname, Address address) {
...
}
とすればより堅い実装となる。
値オブジェクトを不変とする
値オブジェクトの値は上書きしないというのも値オブジェクトを扱う上で重要な考え方で、これをルール化すると、「値を変更する度にオブジェクトそのものを新しく(別のオブジェクトに)する」ということになる。
Nickname nickname = new Nickname("sojiro");
nickname.setNickname("shin-sojiro");
ではなく
Nickname nickname = new Nickname("sojiro");
Nickname updatedNickname = nickname.update("shin-sojiro");
とすることで、各オブジェクトがどの値をもっているのかが明確となり、知らないうちに値が変わっている、ということを防ぐことができる。
コレクションオブジェクト(ファーストクラスコレクション)
値オブジェクトの考え方を発展させ、コレクション型のデータとロジックを独自のクラスに切り出す考え方をコレクションオブジェクト、もしくはファーストクラスコレクションという。
List<User> users = new ArrayList<>();
users.add(user);
のように扱っていた User
のコレクションを、 Users
というクラスを定義し、そのクラスで操作が完結するようにする。
class Users {
List<User> users;
Users(List<User> users) {
this.users = users;
}
Users add(User user) {
List<User> userList = new ArrayList<>(users);
return new Users(userList.add(user));
}
List<User> asList() {
return Collections.unmodifiableList(users);
}
}
ここでのポイントは、
- 値オブジェクトで値に変更がある場合は常に新しいオブジェクトを返したように、コレクションオブジェクトでもコレクションに変更がある場合は新たなオブジェクトを返すようにすること
- コレクションへの参照を返す場合はなるべく変更できない状態にして返すこと
コレクションへの操作を独自クラスに閉じ込め、同一オブジェクトが持つコレクションの内容が変化することのないようにすることでより堅い実装とすることができる。
感想
Perl からプログラミングを始めたこともあってか(言い訳)、オブジェクト指向と言っても型への意識は低く、如何にコンパイル時点でバグを見つけられる堅い実装とするか、その変数に今どのような値が入っているかをコードの書き方で明確にするか、というような意識も低かった。
現場で役立つシステム設計の原則 からは他にも学んだ点が多いので追って記事にできたらと思う。