こんにちは。KOUKIです。
以前、Factory Methodパターンについて記事を書きましたが、別パターンでも実装したので紹介します。

Factory Methodパターン
Factory Methodパターンは、インスタンスの生成に柔軟性を与えるためのパターンです。
サンプルコード
支払いに関するサンプルコードで、Factory Methodパターンの理解を深めましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package factory import "errors" type PaymentMethod interface { Pay(amount float32) string } const ( // ID Cash = 1 DebitCard = 2 ) func GetPaymentMethod(id int) (PaymentMethod, error) { return nil, errors.New("Not implemented yet") } type CashPM struct{} type DebitCardPM struct{} func (c *CashPM) Pay(amount float32) string { return "" } func (d *DebitCardPM) Pay(amount float32) string { return "" } |
このコードでは、CashとDebitCardの2種類の支払い方法があると明示されています。
それぞれ、PaymentMethodインターフェースを実装するメソッド(Pay)を持っており、GetPaymentMethod関数の戻り値として受け取ることができます。
GetPaymentMethod関数のパラメータは支払IDになっており、「1」が渡された場合はCashを、「2」が渡された場合はDebitCardを返すようにできます(それ以外の場合はエラー)。
このように、Interfaceを利用することで柔軟なインスタンスの生成を可能にします。
テストコード
実装はまだ途中ですが、テストコードを書いてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package factory import ( "strings" "testing" ) func TestGetPaymentMethodCash(t *testing.T) { payment, err := GetPaymentMethod(Cash) if err != nil { t.Fatal("A payment method of type 'Cash' must exist") } msg := payment.Pay(10.30) if !strings.Contains(msg, "paid using cash") { t.Error("The cash payment method message wasn't correct") } t.Log("LOG:", msg) } func TestGetPaymentMethodDebitCard(t *testing.T) { payment, err := GetPaymentMethod(DebitCard) if err != nil { t.Fatal("A payment method of type 'DebitCard' must exist") } msg := payment.Pay(22.30) if !strings.Contains(msg, "paid using debit card") { t.Error("The debit card payment method message wasn't correct") } t.Log("LOG:", msg) } func TestGetPaymentMethodNonExistent(t *testing.T) { _, err := GetPaymentMethod(20) if err == nil { t.Error("A payment method with ID 20 must restun an error") } t.Log("LOG:", err) } |
テストを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ go test -v -run=GetPaymentMethod === RUN TestGetPaymentMethodCash factory_test.go:11: A payment method of type 'Cash' must exist --- FAIL: TestGetPaymentMethodCash (0.00s) === RUN TestGetPaymentMethodDebitCard factory_test.go:24: A payment method of type 'DebitCard' must exist --- FAIL: TestGetPaymentMethodDebitCard (0.00s) === RUN TestGetPaymentMethodNonExistent factory_test.go:39: LOG: Not implemented yet --- PASS: TestGetPaymentMethodNonExistent (0.00s) FAIL exit status 1 FAIL factory 0.486s |
「PASS: 1, FAIL: 2」ですね。
インスタンス生成
GetPaymentMethod関数にインスタンスの生成ロジックを実装します。
1 2 3 4 5 6 7 8 9 10 |
func GetPaymentMethod(id int) (PaymentMethod, error) { switch id { case Cash: return new(CashPM), nil case DebitCard: return new(DebitCardPM), nil default: return nil, errors.New(fmt.Sprintf("Payment method %d not recognized\n", id)) } } |
支払いIDに応じてインスタンスを生成し、呼び出し元に返しています。
テストを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
$ go test -v -run=GetPaymentMethod === RUN TestGetPaymentMethodCash factory_test.go:16: The cash payment method message wasn't correct factory_test.go:18: LOG: --- FAIL: TestGetPaymentMethodCash (0.00s) === RUN TestGetPaymentMethodDebitCard factory_test.go:29: The debit card payment method message wasn't correct factory_test.go:31: LOG: --- FAIL: TestGetPaymentMethodDebitCard (0.00s) === RUN TestGetPaymentMethodNonExistent factory_test.go:39: LOG: Payment method 20 not recognized --- PASS: TestGetPaymentMethodNonExistent (0.00s) FAIL exit status 1 FAIL factory 0.579s |
まだエラーが出ますね。ただ、3番目のテストのエラーメッセー(Payment method 20 not recognized)がいい感じになりました。
Payメソッドの実装
Payメソッドを実装しましょう。
1 2 3 4 5 6 7 |
func (c *CashPM) Pay(amount float32) string { return fmt.Sprintf("%0.2f paid using cash\n", amount) } func (d *DebitCardPM) Pay(amount float32) string { return fmt.Sprintf("%0.2f paid using debit card\n", amount) } |
テストを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ go test -v -run=GetPaymentMethod === RUN TestGetPaymentMethodCash factory_test.go:18: LOG: 10.30 paid using cash --- PASS: TestGetPaymentMethodCash (0.00s) === RUN TestGetPaymentMethodDebitCard factory_test.go:31: LOG: 22.30 paid using debit card --- PASS: TestGetPaymentMethodDebitCard (0.00s) === RUN TestGetPaymentMethodNonExistent factory_test.go:39: LOG: Payment method 20 not recognized --- PASS: TestGetPaymentMethodNonExistent (0.00s) PASS ok factory 0.625s |
全てPASSになりました。
おわりに
インスタンス生成を柔軟に実装できるFactory Methodパターンは大変便利です。
例えば、DBのインスタンス生成とかに応用できそうです。MySQL, Redis, PostgreSQL, MongDBなど、複数のDBを切り替えて使用するプロダクトを実装することがあれば、大活躍ですね!
それでは、また!
Go記事まとめ
ソースコード
factory.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package factory import ( "errors" "fmt" ) type PaymentMethod interface { Pay(amount float32) string } const ( // ID Cash = 1 DebitCard = 2 ) func GetPaymentMethod(id int) (PaymentMethod, error) { switch id { case Cash: return new(CashPM), nil case DebitCard: return new(DebitCardPM), nil default: return nil, errors.New(fmt.Sprintf("Payment method %d not recognized\n", id)) } } type CashPM struct{} type DebitCardPM struct{} func (c *CashPM) Pay(amount float32) string { return fmt.Sprintf("%0.2f paid using cash\n", amount) } func (d *DebitCardPM) Pay(amount float32) string { return fmt.Sprintf("%0.2f paid using debit card\n", amount) } |
factory_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package factory import ( "strings" "testing" ) func TestGetPaymentMethodCash(t *testing.T) { payment, err := GetPaymentMethod(Cash) if err != nil { t.Fatal("A payment method of type 'Cash' must exist") } msg := payment.Pay(10.30) if !strings.Contains(msg, "paid using cash") { t.Error("The cash payment method message wasn't correct") } t.Log("LOG:", msg) } func TestGetPaymentMethodDebitCard(t *testing.T) { payment, err := GetPaymentMethod(DebitCard) if err != nil { t.Fatal("A payment method of type 'DebitCard' must exist") } msg := payment.Pay(22.30) if !strings.Contains(msg, "paid using debit card") { t.Error("The debit card payment method message wasn't correct") } t.Log("LOG:", msg) } func TestGetPaymentMethodNonExistent(t *testing.T) { _, err := GetPaymentMethod(20) if err == nil { t.Error("A payment method with ID 20 must restun an error") } t.Log("LOG:", err) } |
コメントを残す
コメントを投稿するにはログインしてください。