こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるFactoryパターンについて、紹介しています。
<目次>
デザインパターン
シチュエーション
Factoryパターンは、インスタンスの生成に関するデザインパターンです。
例えば、Person構造体を実装したとしましょう。
1 2 3 4 5 6 |
package main type Person struct { Name string Age int } |
これを使うためには、次のようにインスタンス化します。
1 2 3 |
func main() { p := Person{"SelfNote", 32} } |
しかし、Person構造体にパラメータが増えるとどうなるでしょう。
1 2 3 4 5 |
type Person struct { Name string Age int Money int // 追加 } |
この場合は、以下のように値を追加する必要があります。
1 2 3 |
func main() { p := Person{"SelfNote", 32, 10000} } |
3つだけならまだ許容できますが、このパラメータが増え続けていったらゾッとしますよね?
誤った情報を渡すことでバグになるかもしれないので、これを回避できれば最高です!
Factory Function
インスタンスの生成には、コンストラクタ関数を作成しましょう。
そして、ユーザーが手動で設定する必要のないパラメータは、デフォルト値として定義すれば良さそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import "fmt" type Person struct { Name string Age int Money int } // コンストラクタ func NewPerson(name string, age int) *Person { return &Person{ Name: name, Age: age, Money: 1000, // デフォルト値 } } func main() { p := NewPerson("selfnote", 32) fmt.Println(p) } |
これでコードの呼び出し側は、NewPersonを呼び出すことで、Moneyにデフォルトの値(1000)が入った状態のインスタンスをいつでも取得できるようになります。
Name/Ageが動的に変わる(ユーザーが生成したい)データを渡すことがポイントで、馬鹿正直に全てのデータを呼び出し元で用意する必要はなく、というよりむしろ、ユーザーがインスタンスの詳細を把握していなくてもName/Ageを渡すということだけ知っていれば、このPerson構造体を使えるぜ!ということなんです。
つまり、インスタンス生成が簡単になるわけですね。今回は、例が簡単すぎてその威力を計りかねると思いますが、このような考え方は、シンプルなプログラムを実装する上で、重要だったりします。
Factory Interface
先ほどのコードには、2つのいけていない点があります。
1つは、Person構造体がPublicで定義されているので、外部から値を自由に変更できることです。
1 2 3 4 5 6 7 |
func main() { p := NewPerson("selfnote", 32) fmt.Println(p) p.Name = "Other Person" fmt.Println(p) } |
1 2 3 |
$ go run main.go &{selfnote 32 1000} &{Other Person 32 1000} |
パラメータが呼び出し元で自由に変更できると、予期しないバグが発生することがあるので、あまり変更されたくありません。
もう一つは、作成できるインスタンスの種類がたったの一つだということです。何故なら、NewPersonの戻り値がPerson構造体になっているためです。
1 2 |
// Person構造体以外は返せない func NewPerson(name string, age int) *Person {} |
例えば、新たにRichPerson/PoorPersonを生成したい場合は、コンストラクタを追加する必要があります。
1 2 |
func NewRichPerson(name string, age int) *RichPerson {} func NewPoorPerson(name string, age int) *PoorPerson {} |
これを解決するために、Interfaceを活用しましょう!
まず、先ほどのサンプルコードを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 |
package main import "fmt" // Publicでinterfaceを定義 type Person interface { SayMySelf() } // privateでperson/パラメータを定義 // こうすることで呼び出し元でパラメータの変更が不可 type person struct { name string age int money int } func (p *person) SayMySelf() { fmt.Println("I am Normal Person!") } // 戻り値をPersonインターフェースにしたコンストラクタ func NewPerson(name string, age, money int) Person { return &person{ name: name, age: age, money: money, } } func main() { p := NewPerson("selfnote", 32, 100) p.SayMySelf() } |
1 2 |
$ go run main.go I am Normal Person! |
復習ですが、Go言語のPublic/Privateの宣言は、先頭の文字列が大文字か小文字かで区別されます。
1 2 3 4 5 |
// Public Person // Private person |
ここまでで、呼び出し元でパラメータを自由に変更できる問題を解決したことになります。

また、Interfaceに宣言したSayMySelfを紐付けたい構造体のメソッドとして定義すれば、Interfaceを実装したことになります。
1 2 3 4 5 |
// person構造体がSayMySelfをメソッドとして定義したので、 // Interfaceを実装したということになる func (p *person) SayMySelf() { fmt.Println("I am Normal Person!") } |
コンストラクタの戻り値をPerson Interfaceにしていますが、この紐付けが完了したので、person構造体を返すことができます。
1 2 3 4 |
// Person Intefaceを実装した構造体を返却できるようにした func NewPerson(name string, age, money int) Person { return &person{...} } |
こうすることで、以下のようなことが行えます。
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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
package main import "fmt" type Person interface { SayMySelf() } type person struct { name string age int money int } func (p *person) SayMySelf() { fmt.Println("I am Normal Person!") } // richPerson追加 type richPerson struct { person person } // Interfaceを実装 func (p *richPerson) SayMySelf() { fmt.Println("I am Rich Person!") } // poorPerson追加 type poorPerson struct { person person } // Interfaceを実装 func (p *poorPerson) SayMySelf() { fmt.Println("I am Poor Person!") } func NewPerson(name string, age, money int) Person { p := person{ name: name, age: age, money: money, } // 条件によって返却するインスタンスを切り替える if money > 99 { return &richPerson{ person: p, } } else if 0 < money && money < 99 { return &p } else { return &poorPerson{ person: p, } } } func main() { // moneyの値で取得するインスタンスを選択できる p := NewPerson("selfnote", 32, 100) p.SayMySelf() p = NewPerson("selfnote", 32, 50) p.SayMySelf() p = NewPerson("selfnote", 32, -100) p.SayMySelf() } |
1 2 3 4 |
$ go run main.go I am Rich Person! I am Normal Person! I am Poor Person! |
新しくrichPerson/poorPerson構造体を定義し、Interfaceと紐づけるため、それぞれにSayMySelfをメソッドとして実装しました。
そして、NewPerson コンストラクタで、条件によって返却される構造体を変更する処理を追加しています。
この実装パターンは、DBの切り替え処理(MySQL -> PostgreSQLみたいな)などで利用できるので大変便利です。
次回
次回は、Factoryパターンの後編になります。
Go言語まとめ
ソースコード
Factory Function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package main import "fmt" type Person struct { Name string Age int Money int } func NewPerson(name string, age int) *Person { return &Person{ Name: name, Age: age, Money: 1000, } } func main() { p := NewPerson("selfnote", 32) fmt.Println(p) } |
Factory 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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package main import "fmt" type Person interface { SayMySelf() } type person struct { name string age int money int } func (p *person) SayMySelf() { fmt.Println("I am Normal Person!") } type richPerson struct { person person } func (p *richPerson) SayMySelf() { fmt.Println("I am Rich Person!") } type poorPerson struct { person person } func (p *poorPerson) SayMySelf() { fmt.Println("I am Poor Person!") } // 戻り値をPersonインターフェースにしたコンストラクタ func NewPerson(name string, age, money int) Person { p := person{ name: name, age: age, money: money, } if money > 99 { return &richPerson{ person: p, } } else if 0 < money && money < 99 { return &p } else { return &poorPerson{ person: p, } } } func main() { p := NewPerson("selfnote", 32, 100) p.SayMySelf() p = NewPerson("selfnote", 32, 50) p.SayMySelf() p = NewPerson("selfnote", 32, -100) p.SayMySelf() } |
コメントを残す
コメントを投稿するにはログインしてください。