こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるFactoryパターンについて、紹介しています。
<目次>
デザインパターン
Factory Generator
前回の実装では、「利用者側で必要なデータを揃える -> コンストラクタを呼ぶ(インスタンス化) -> インスタンスを利用」のような使い方でした。
しかし、このインスタンス化したデータを作成するのに、コンストラクタを毎回呼び出す必要があります。
大抵の場合はそれでOKなのですが、次のシチュエーションの場合はどうでしょうか。
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 |
package main import "fmt" type HtmlTag struct { tagName, id, class string } func NewHtmlTag(tagName, id, class string) *HtmlTag { return &HtmlTag{ tagName: tagName, id: id, class: class, } } func main() { // HTMLのフォーム forms := []HtmlTag{} // フォーム要素を作りたいが、何度もNewHtmlTagを呼び出す必要がある inputTag := NewHtmlTag("input", "id-name", "input-form") emailTag := NewHtmlTag("input", "id-email", "input-form") forms = append(forms, *inputTag, *emailTag) fmt.Println(forms) } |
1 2 |
$ go run main.go [{input id-name input-form} {input id-email input-form}] |
HTMLのフォームをイメージしてください。formsをWebブラウザに返却するとその構成要素がフォームとなって描画されるイメージです。
このプログラムでは、フォーム要素を作成するために、何度もNewHtmlTagコンストラクタを呼び出しています。しかし、コンストラクタの呼び出しは一回で済ませたいところです。
というわけで、以下のように実装してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package main import "fmt" type HtmlTag struct { tagName, id, class string } // コンストラクタ func NewHtmlTagFactory(tagName, class string) func(id string) *HtmlTag { return func(id string) *HtmlTag { return &HtmlTag{tagName, id, class} } } |
NewHtmlTagFactoryの戻り値を関数にしました。そうすることで、コンストラクタの呼び出しは一度のみで、先ほどと同じことが行えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func main() { // HTMLのフォーム forms := []HtmlTag{} // コンストラクタの呼び出し inputFactory := NewHtmlTagFactory("input", "input-form") inputTag := inputFactory("id-name") emailTag := inputFactory("id-email") forms = append(forms, *inputTag, *emailTag) fmt.Println(forms) } |
1 2 |
$ go run main.go [{input id-name input-form} {input id-email input-form}] |
今回は、input タグを作りましたが、textareaタグなど他のタグを作りたい場合は、次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func main() { // HTMLのフォーム forms := []HtmlTag{} // コンストラクタの呼び出し inputFactory := NewHtmlTagFactory("input", "input-form") inputTag := inputFactory("id-name") emailTag := inputFactory("id-email") forms = append(forms, *inputTag, *emailTag) // 別のタグを呼び出す textAreaFactory := NewHtmlTagFactory("textarea", "textarea-form") descriptionTag := textAreaFactory("id-description") forms = append(forms, *descriptionTag) fmt.Println(forms) } |
1 2 |
$ go run main.go [{input id-name input-form} {input id-email input-form} {textarea id-description textarea-form}] |
使いやすいですよね。
NewHtmlTagFactoryコンストラクタの戻り値を関数にしていますが、これだとわかりずらい!という方もいると思うので、以下のように実装してもOKです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Factory構造体を定義(id以外) type HtmlTagFactory struct { tagName, class string } // Buildするメソッドを用意 func (f *HtmlTagFactory) Create(id string) *HtmlTag { return &HtmlTag{f.tagName, id, f.class} } func NewHtmlTagFactory2(tagName, class string) *HtmlTagFactory { return &HtmlTagFactory{tagName, class} } |
NewHtmlTagFactory2では、HtmlTagFactory構造体を返すように実装しました。少しは、実装がシンプルになった気がしますよね。
1 2 3 4 5 6 7 8 9 |
func main() { ... btnFactory := NewHtmlTagFactory2("button", "button-form") submitBtn := btnFactory.Create("id-submit") forms = append(forms, *submitBtn) fmt.Println(forms) } |
1 2 3 4 5 6 7 |
$ go run main.go [ {input id-name input-form} {input id-email input-form} {textarea id-description textarea-form} {button id-submit button-form} ] |
Prototype Factory
生成できるTagの種類を定数(プロトタイプ)として設定するやり方もあります。
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 |
package main import "fmt" type HtmlTag struct { tagName, id, class string } // 先に生成できるTagを定義する const ( InputTag = iota TextAreaTag ButtonTag ) // コンストラクタ func NewHtmlTag(tagType int) *HtmlTag { switch tagType { case InputTag: return &HtmlTag{"input", "", "input-form"} case TextAreaTag: return &HtmlTag{"textarea", "", "textarea-form"} case ButtonTag: return &HtmlTag{"button", "", "button-form"} default: panic("Unsupported Tag Type") } } |
Go言語のiota識別子で、連番の数値(0 ~ 2)を生成しています。これをコンストラクタ内のSwitch文のcaseに指定することで、コンストラクタに与えられたパラメータによって、生成するインスタンスを選択できるようにしました。
このコードは、次のように使います。
1 2 3 4 5 |
func main() { inputTag := NewHtmlTag(InputTag) inputTag.id = "id-email" fmt.Println(inputTag) } |
1 2 |
$ go run main.go &{input id-email input-form} |
InputTagを定数で宣言しているので、NewHtmlTagの引数として渡せます。しかも数値ではなく、”InputTag“という文字列で渡せるので処理がわかりやすいと思います。
このパターンも便利そうです。例えば、AWS/GCP/Azuraへ接続を行いたいとなった時に、以下のようにわかりやすく実装できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
const ( AWS = iota GCP Azura ) func NewCloudConnect(croudType int) *XXXX { .... } func main() { // クラウドへのコネクション情報を取得する awsConnect := NewCloudConnect(AWS) ... } |
まとめ
Factoryパターンは、インスタンスの生成をわかりやすく実装する上で重宝しそうなパターンですね。
今までは適当(?)にコンストラクタを作成していましたが、このパターンを取り入れて、もっと実用的なプログラミングを行えるようになりたいと思います。
Go言語まとめ
ソースコード
Factory Generator
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 42 43 44 45 46 47 48 49 50 |
package main import "fmt" type HtmlTag struct { tagName, id, class string } func NewHtmlTagFactory(tagName, class string) func(id string) *HtmlTag { return func(id string) *HtmlTag { return &HtmlTag{tagName, id, class} } } // Factory構造体を定義(id以外) type HtmlTagFactory struct { tagName, class string } // Buildするメソッドを用意 func (f *HtmlTagFactory) Create(id string) *HtmlTag { return &HtmlTag{f.tagName, id, f.class} } func NewHtmlTagFactory2(tagName, class string) *HtmlTagFactory { return &HtmlTagFactory{tagName, class} } func main() { // HTMLのフォーム forms := []HtmlTag{} // コンストラクタの呼び出し inputFactory := NewHtmlTagFactory("input", "input-form") inputTag := inputFactory("id-name") emailTag := inputFactory("id-email") forms = append(forms, *inputTag, *emailTag) // 別のタグを呼び出す textAreaFactory := NewHtmlTagFactory("textarea", "textarea-form") descriptionTag := textAreaFactory("id-description") forms = append(forms, *descriptionTag) btnFactory := NewHtmlTagFactory2("button", "button-form") submitBtn := btnFactory.Create("id-submit") forms = append(forms, *submitBtn) fmt.Println(forms) } |
Prototype Factory
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 |
package main import "fmt" type HtmlTag struct { tagName, id, class string } // 先に生成できるTagを定義する const ( InputTag = iota TextAreaTag ButtonTag ) func NewHtmlTag(tagType int) *HtmlTag { switch tagType { case InputTag: return &HtmlTag{"input", "", "input-form"} case TextAreaTag: return &HtmlTag{"textarea", "", "textarea-form"} case ButtonTag: return &HtmlTag{"button", "", "button-form"} default: panic("Unsupported Tag Type") } } func main() { inputTag := NewHtmlTag(InputTag) inputTag.id = "id-email" fmt.Println(inputTag) } |
コメントを残す
コメントを投稿するにはログインしてください。