まえがき
最近業務でletを使ってコードを組んでいたら、「factoryで書いた方が良さそうですね」というレビューをいただいた。
letでは再代入を意識しないといけないため、できるだけconstで書きたいかららしい。
とりあえずレビューいただいた通りでコードを書いたが、結局factoryとは何で、副作用はないのか、書くとき気にする必要のあるものはあるかなど疑問に残ることが多かった。
そのため、記事としてまとめてみた。
factoryパターンとは?
factoryパターンとは、オブジェクト指向プログラミングにおけるデザインパターンの一つ。
主な目的は、オブジェクトの生成プロセスを抽象化すること。
要するに、オブジェクトを直接生成する代わりにメソッドなどを通じてオブジェクトを生成することである。
コードで見る
例として以下のようなケースを考えてみた。
決済の時、どのお支払い方法で支払われたかを判断するコードになっている。
interface PaymentMethod {
processPayment(amount: number): string;
}
class CreditCardPayment implements PaymentMethod {
processPayment(amount: number): string {
return `Processing $${amount} via Credit Card`;
}
}
class PayPayPayment implements PaymentMethod {
processPayment(amount: number): string {
return `Processing $${amount} via PayPay`;
}
}
class CashPayment implements PaymentMethod {
processPayment(amount: number): string {
return `Processing $${amount} via Cash`;
}
}
letで実装
letで書いたら以下のようになる。実行されるメソッド内で直接new
を使ってオブジェクトを生成することがわかる。
function processPayment(method: string, amount: number): string {
let paymentMethod: PaymentMethod;
switch (method) {
case "creditcard":
paymentMethod = new CreditCardPayment();
break;
case "paypay":
paymentMethod = new PayPayPayment();
break;
case "cash":
paymentMethod = new CashPayment();
break;
default:
throw new Error("Unknown payment method");
}
return paymentMethod.processPayment(amount);
}
factoryで実装
今回はクラスでfactoryを実装してみた。
このfactoryクラスの中で、各オブジェクトを作成し、実行されるメソッドに返す形となっている。
class PaymentMethodFactory {
static createPaymentMethod(method: string): PaymentMethod {
switch (method.toLowerCase()) {
case "creditcard":
return new CreditCardPayment();
case "paypay":
return new PayPayPayment();
case "cash":
return new CashPayment();
default:
throw new Error("Unknown payment method");
}
}
}
function processPaymentWithFactory(method: string, amount: number): string {
const paymentMethod = PaymentMethodFactory.createPaymentMethod(method);
return paymentMethod.processPayment(amount);
}
いいところが多い
新しいタイプのオブジェクトを追加する際に、既存のコードを変更せずに拡張できる。
また、実行されているメソッドで直接オブジェクトを生成しないため、結合度が低くなる。
そのため、オブジェクト生成に関するロジックを一箇所にまとめることができそう。
これは万能なの?
とにかくletで済むものをclassやclosureで書くことになるため、パフォーマンスは落ちることになる。意識せず乱用すると無駄なインスタンスが増えてメモリリークにも繋がる。
また、ユニットテストを書くときに、ファクトリーをモックする必要がある。
テストを書くときにモックの扱いで一番ハマることが多いので、初期学習コストが高くなる。
あと、なんとなく抽象化するときの粒度を考えるのが難しそうだった。
例えば今回のコードでcredit card, paypay, cashが一緒だったけど、「現金は通信が行われないから別だろ」という意見があるかもしれない。
そもそもクラスのprivateで書いた方がパフォーマンスがいいからあまり使われなくなった、という記事も結構見つけた。やっぱパフォーマンスは大事
おわりに
コードを書くときはなんとなく理解したつもりでいたが、記事を書いていてやはり全然わかっていなかったことがわかった。
知識をまとめることは大事だなと