🔥
クラスのコンストラクタはオブジェクトを渡すようにしたらよいのではないか?と思った話
前提
クラスのコンストラクタでは、引数の数と初期化する値の数が同じことが多いと思います
例
class Account {
name: string;
email: string;
constructor(name: string, email) {
this.name = name;
this.email = email;
}
}
const account = new Account("山田太郎", "yamada@example.com")
よくあるクラスの書き方だと思います
課題
これまで業務や個人で同様のコードを書いてきましたが、以下の点が気になっていました
- 引数の順序を誤った場合、コンパイルエラーにならないケースがある
- Webアプリのようにリクエストデータをオブジェクトで受け取る場合に、オブジェクト→コンストラクタの引数に変換する必要がある
個人的に2つ目に挙げたオブジェクトをコンストラクタの引数に渡す際に、少々やりずらさを感じていました
例
const input = {
name: "山田太郎",
email: "yamada@example.com"
}
const account = new Account(input.name, input.email)
引数の数が少ないうちは問題ないですが、数が多くなってくると、煩雑になってしまいます
コンストラクタをオブジェクトにしてみる
それでは、オブジェクトで渡す方法をみてみましょう
class Account {
name: string;
email: string;
constructor(props: {name: string, email: string}) {
this.name = props.name;
this.email = props.email;
}
}
// 直接引数を指定して初期化する場合
const account = new Account({ name: "山田太郎", email: "yamada@example.com" })
// 入力オブジェクトから初期化する場合
const input = {
name: "山田太郎",
email: "yamada@example.com"
}
const account = new Account(input)
コンストラクタでオブジェクトを渡すようにしてみました
直接引数を指定して初期化する場合、キーを指定しなければならず冗長になってしまいました
利点
ただ、inputのようなオブジェクト形式のデータの場合、引数をコンストラクタに合わせて渡す必要がなく、そのまま渡すことができます
また、キー・バリューの形式で渡すため、引数の順番を間違えて初期化するの可能性も低くなると思います
[応用]クラスにバリデーションを追加してみる
クラスにバリデーションを追加します
今回は個人的に好きなライブラリのzodを利用して、モデルクラスのバリデーションを実装してみます
コンストラクタの引数がオブジェクトの場合、zodを容易に組み込んでバリデーションを実装できます
// バリデーションに使うスキーマ
// Accountクラスのフィールドのバリデーションに使用する
const schema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
})
// zodのスキーマからコンストラクタの型を抽出
type AccountProps = z.input<typeof schema>
class Account {
name: string;
email: string;
constructor(props: AccountProps) {
this.name = props.name;
this.email = props.email;
this.validate();
}
changeEmail(email: string) {
this.email = email;
this.validate();
}
validate() {
const result = schema.safeParse(this);
if (!result.success) {
throw new Error("Validation Error");
}
}
}
validate
メソッドで、zodで定義したスキーマを利用して、インスタンスのフィールドが適切な値かをチェックしています
このvalidate
メソッドをクラスの初期化時や、インスタンスメソッドの呼び出し後に実行することで、安全にインスタンスの生成や変更を行うことができます
以上、クラスのコンストラクタの引数をオブジェクトにする利点を書いてみました
Discussion