こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるBridgeパターンについて紹介します。
デザインパターンまとめ
シチュエーション
例えばですが、リソースを読み込む処理を実装したいとします。
リソースは何でもいいです。APIからでもJSONファイルからでも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 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 |
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" ) // リソース type UserInfo struct { AvatarURL string `json:"avatar_url"` Name string `json:"name"` } // GitHub REST APIからリソースを読み込む type HttpRequest struct{} // コンストラクタ func NewHttpRequest() *HttpRequest { return &HttpRequest{} } func (h *HttpRequest) ShowUser() { var user UserInfo resp, _ := http.Get("https://api.github.com/users/golang") defer resp.Body.Close() _ = json.NewDecoder(resp.Body).Decode(&user) fmt.Println( fmt.Sprintf("HTTP -> Name: %s, URL: %s", user.Name, user.AvatarURL)) } // JSONファイルからリソースを読み込む type JSONRequets struct{} // コンストラクタ func NewJSONRequest() *JSONRequet { return &JSONRequet{} } func (j *JSONRequet) ShowUser() { raw, _ := ioutil.ReadFile("./resource.json") var user UserInfo json.Unmarshal(raw, &user) fmt.Println( fmt.Sprintf("JSON -> Name: %s, URL: %s", user.Name, user.AvatarURL)) } func main() { hr := NewHttpRequest() hr.ShowUser() jr := NewJSONRequest() jr.ShowUser() } |
HttpRequest構造体は、GitHub REST APIからユーザー情報を取得します。
また、JSONRequest構造体は、JSONファイルからユーザー情報を取得します。resource.jsonファイルの中身は、こんな感じです。
1 2 3 4 |
{ "name": "Go", "avatar_url": "https://avatars.githubusercontent.com/u/4314092?v=4" } |
プログラムを実行してみましょう。
1 2 3 |
$ go run main.go HTTP -> Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 JSON -> Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 |
このソースコード自体は素晴らしいもの^^です。しかし、改善すべき点もあります。
例えば、他リソース(CSVやDBなど)からの読み込みもしたいとなった場合、コンストラクタはそれに付随して増えていくでしょう。
開発の規模が小さければ問題にならないかもしれませんが、そうでない場合、保守や改修作業が難しくなっていくかもしれません。
加えて、構造体の一つ一つが「どんな機能(メソッド)を持つか」についても把握することが難しくなるでしょう。
Bridgeパターン
Bridgeは「橋」を指します。機能の橋渡しをするパターンなんですね、ざっくり言うと^^
インターフェースを定義
最初にするべきことは、橋渡しをするためのインターフェースを定義することです。
1 2 3 4 |
// 機能橋渡しのためのインターフェース type Resource interface { ShowUser() } |
共通の構造体を定義
次に、共通の構造体を定義します。
1 2 3 4 |
// 共通の構造体 type Reader struct { resrouce Resource } |
ここには、先ほど定義したインターフェースをプロパティとして埋め込んでおきます。Bridgeパターンは、機能の橋渡しです。ここ、重要ですよ^^
コンストラクタとメソッドを定義
Reader構造体のコンストラクタを定義しましょう。
1 2 3 4 5 6 7 8 |
// コンストラクタ func NewReader(r Resource) *Reader { return &Reader{resrouce: r} } func (r *Reader) Read() { r.resrouce.ShowUser() } |
このコンストラクタは、Resourceインターフェースをパラメータとして持っています。つまり、ShowUserメソッドを実装した構造体以外は、呼び出し時に引数として渡すことができません。
そして、ReadメソッドからShowUserメソッドを呼び出します。
ちなみに、Reader構造体にShowUserメソッドを実装していません。初めて知ったのですが、構造体のプロパティにShowUserメソッドを実装した構造体のプロパティを埋め込むことで、インターフェースの実装を満たしたことになるようです。目から鱗ですね^^
使ってみよう
main関数を以下のように書き換えます。
1 2 3 4 5 6 7 8 9 |
func main() { hr := HttpRequest{} r := NewReader(&hr) r.Read() jr := JSONRequet{} r = NewReader(&jr) r.Read() } |
今度は、共通のコンストラクタ(NewReader)からインスタンスを生成しました。
そして、Readメソッドを呼び出すことで、下記の結果を得られます。
1 2 3 |
$ go run main.go HTTP -> Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 JSON -> Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 |
OKですね。
HttpRequest/JSONRequet構造体が共通して持っているユーザー情報表示機能をReader構造体を経由(橋渡し)して呼び出すことに成功しました^^
補足
実は、HttpRequest/JSONRequet構造体のShowUserを以下のように呼び出すこともできます。
1 2 3 4 5 6 |
func main() { hr := HttpRequest{} jr := JSONRequet{} hr.ShowUser() jr.ShowUser() } |
もしかしたら、これで良いじゃん!と思うかもしれません。
確かに今回定義した構造体は、パラメータの設定が不要だったので、コンストラクタがなくても問題なくインスタンス化できるかもしれません。
しかし、コンストラクタは、インスタンスの生成時に行いたい処理を定義する場所で、必須パラメータの設定や初期化処理などが記述できます。その為、なるべくコンストラクタを定義した方が良いと個人的には思っています^^
まとめ
今回は、もしかしたら例が分かりづらく、Bridgeパターンの有用性を今ひとつお伝えしきれていないかもしれません。
ユーザー情報の設定/更新/削除など機能を増やしていけば、より明確にイメージできると思います。
共通のコンストラクタを定義したことで、インスタンスの生成を一元管理できますし、Resourceインターフェースが機能一覧になるので、構造体がどんな機能を持っているか分かりやすくなっていると思います。
1 2 3 4 5 6 7 |
// 機能橋渡しのためのインターフェース type Resource interface { ShowUser() SetUser(name string) DeleteUser(id int) UpdateUser(name string) } |
このパターンは、DBの切り替え処理(MySQL -> PostgreSQL)などにも応用できる考え方なので、習得しておいて損はないと思います。
それでは、また!
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 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 |
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" ) // リソース type UserInfo struct { AvatarURL string `json:"avatar_url"` Name string `json:"name"` } // 機能橋渡しのためのインターフェース type Resource interface { ShowUser() } // 共通の構造体 type Reader struct { resrouce Resource } // コンストラクタ func NewReader(r Resource) *Reader { return &Reader{resrouce: r} } // ユーザー情報を読み込む func (r *Reader) Read() { r.resrouce.ShowUser() } // HTTP Requestからリソースを読み込む type HttpRequest struct{} func (h *HttpRequest) ShowUser() { var user UserInfo resp, _ := http.Get("https://api.github.com/users/golang") defer resp.Body.Close() _ = json.NewDecoder(resp.Body).Decode(&user) fmt.Println(fmt.Sprintf("HTTP -> Name: %s, URL: %s", user.Name, user.AvatarURL)) } // JSONファイルからリソースを読み込む type JSONRequet struct{} func (j *JSONRequet) ShowUser() { raw, _ := ioutil.ReadFile("./resource.json") var user UserInfo json.Unmarshal(raw, &user) fmt.Println(fmt.Sprintf("JSON -> Name: %s, URL: %s", user.Name, user.AvatarURL)) } func main() { hr := HttpRequest{} r := NewReader(&hr) r.Read() jr := JSONRequet{} r = NewReader(&jr) r.Read() } |
コメントを残す
コメントを投稿するにはログインしてください。