定数フィールドを使って非破壊メソッドを実装するようにすればエンティティクラスのgetterは要らなくなる
エンティティクラスを実装する場面で、外部から参照可能であるが、変更不可能または変更時に条件を満たす必要があるフィールドを実現したい場合を考える。
従来の方法
フィールド自体はprivateフィールドにして、そのフィールドの値を読み書きするためのgetterメソッドとsetterメソッドを実装するというアプローチがよく取られる。
問題点
一つのフィールドを実現するためにフィールドとメソッドを合わせて2つないし3つ実装しなければならないのは、クラスに本来の関心事とは異なるコードを含めることになると感じられる。
publicなgetterメソッドを持つprivateフィールドと、本当にprivateフィールドとして使いたいprivateフィールドの区別がつきにくい。
privateなgetterメソッドを作って、getterメソッドの可視性で見分ければ良い気もするが。
code:ts
class User {
private _email: string;
private _password: string;
public constructor(params: { email: string; password: string }) {
this._email = params.email;
this._password = params.password;
}
public get email(): string {
return this._email;
}
public set email(value: string) {
if (!isEmail(value)) {
throw new Error("${value}" is not a valid email address.);
}
this._email = email;
}
// 以下省略
}
mgn901.iconの方法
外部から参照可能であるが変更不可能なフィールドは、publicな定数フィールドにすれば実現できる。
TypeScriptの場合はpublic readonly
Javaの場合はpublic final
変更時に条件を満たす必要があるフィールドを実現したい場合は、破壊的なsetterの代わりに、そのフィールドに変更が適用された新しいオブジェクトを返す非破壊メソッドを実装する。 code:ts
class User {
public readonly email: string;
public readonly password: string;
public constructor(params: { email: string; password: string }) {
this.email = email;
this.password = password;
}
public toEmailUpdated(value: string): User {
if (!isEmail(value)) {
throw new Error("${value}" is not a valid email address.);
}
return new User({
email: value,
password: this.password;
});
}
// 以下省略
}
インスタンス化を行うのでコード量が増してしまう点は残念であるが、関数型プログラミングで実現されるような、関数を呼び出した結果のわかりやすさは確保できるかもしれない。