こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるAdapterパターンについて、紹介しています。
シチュエーション
Adapterパターンのイメージは、変換機ですね。

例えば、PCにスマートフォンを繋ぎたい場合、私たちはUSB Adapterを使うと思います。Adapterのおかげで、PC(インターフェース)の変更なしで、機能を利用することができるようになるわけですね。
この考え方は、プログラムの世界でも活躍します。どこかの誰かが大昔に作ったプログラムを「変更なし」で利用したい場合、Adapterを作って対処します。
つまり、「元々の構造体の振る舞いを変えたくないけど、それを使った新しい機能を実装したいなぁ」となったときに、威力を発揮します。
Adapterの実装
Adapterパターンを実装していきましょう。
リソースを読み込むための構造体を定義
とあるリソース(情報源)からユーザー情報を読み込むための構造体を定義しましょう。
1 2 3 4 5 6 7 8 9 10 |
package main import ( "encoding/json" "fmt" "net/http" ) // リソースを読み込むためのクラス type Reader struct{} |
このReader構造体に、ユーザー情報を表示するメソッド実装したいと思います。提供されるリソースの形式は不明なので、interfaceをパラメータとして渡します。
1 2 3 4 5 6 7 8 |
type Resource interface { GetUser() UserInfo } func (r *Reader) ReadResource(re Resource) { user := re.GetUser() fmt.Println(fmt.Sprintf("Name: %s, URL: %s", user.Name, user.AvatarURL)) } |
リソース構造体 ~ その一 ~
さて、先ほど作成したReadResourceに渡せる構造体を作成しましょう。
1 |
type HttpRequest struct{} |
これは、GitHub REST APIに対して、リクエスト送信する構造体です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
type UserInfo struct { AvatarURL string `json:"avatar_url"` Name string `json:"name"` } func (h *HttpRequest) GetUser() UserInfo { var u UserInfo resp, err := http.Get("https://api.github.com/users/golang") if err != nil { return u } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&u) if err != nil { return u } return u } |
リクエストに成功したらGitHabリポジトリ名とURLがUserInfo構造体にバインドされます。
確認作業1 ~Adapterパターン実装前~
まだ、AdapterのAの字も出てきてませんが、ここまでで実装したコードが動くか確認してみましょう。
1 2 3 4 5 6 |
func main() { r := &Reader{} h := &HttpRequest{} r.ReadResource(h) } |
1 2 |
$ go run main.go Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 |
OKですね。何度も言うようですが、ReadResourceメソッドの引数はインターフェースなので、GetUserメソッドを実装したインスタンスしか渡すことができません。
リソース構造体 ~ その二 ~
次に、以下の構造体を作成しましょう。この構造体は、元々別のどこかで実装してあったものと仮定してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
type JSONRequets struct{} func (j *JSONRequets) ReadLocalJSON() UserInfo { raw, err := ioutil.ReadFile("./resource.json") if err != nil { fmt.Println(err.Error()) return UserInfo{} } var u UserInfo json.Unmarshal(raw, &u) return u } |
Adapterパターンが必要とされるわけ
JSONRequetsは、GetUserメソッドを実装していないので、ReadResourceメソッドの引数として渡すことができません。
1 2 3 4 5 6 7 8 9 |
func main() { r := &Reader{} h := &HttpRequest{} r.ReadResource(h) j := &JSONRequets{} r.ReadResource(j) // コンパイルエラー } |

ReadResourceメソッドにJSONRequetsを渡すにはどうしたらいいでしょうか。
普通は、JSONRequets構造体にGetUserメソッドを実装すれば?と思うかもしれません。
1 2 |
// 例 func (j *JSONRequets) GetUser() UserInfo {...} |
しかし、仮に、そう仮にです。JSONRequetsが「他部署のエンジニア(チーム外)や別の業者」により実装され、安易に機能変更ができない場合は、どうすれば良いでしょうか?あるいは、他のプログラムでメチャクチャ使われている重要な構造体で、機能変更するためには、様々な申請手続きを行わなくてはならなかったとしたら?
めんどくせぇぇええええ!!!ですよね(笑)。そんな感じでイメージしてもらえれば幸いです^^
このような時に、Adapterパターンが活躍します。
Adapterを実装
Adapterパターンは、既存の処理(インターフェース)に変更を加えず、よろしくやってくれる実装パターンです。
イメージは「変換機」なので、以下のように実装しましょう。
1 2 3 4 5 6 7 |
type JSONAdapter struct { jsonReader *JSONRequets } func (j JSONAdapter) GetUser() UserInfo { return j.jsonReader.ReadLocalJSON() } |
これで、「JSONRequets -> JsonAdapter -> GetUserメソッドを実装した構造体」の構図になりました。
ここで実装したGetUserメソッドからReadLocalJSONメソッドを呼び出しているところがミソです。
確認作業2 ~Adapterパターン実装後~
先ほど実装したJsonAdapter構造体を使ってみましょう。
1 2 3 4 5 6 7 8 9 10 |
func main() { r := &Reader{} h := &HttpRequest{} fmt.Print("HTTP Request: ") r.ReadResource(h) j := &JSONRequets{} fmt.Print("JSON: ") r.ReadResource(&JSONAdapter{j}) } |
ReadResourceメソッドには、GetUserメソッドを実装したインスタンスを渡せます。そのため、JSONAdapterインスタンスのパラメータにJSONRequetsインスタンスを含めて渡すことで、既存の処理を一切修正する事なく、ReadResourceメソッドを利用できるというわけです。
ローカルに、「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 Request: Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 JSON: Name: Go, URL: https://avatars.githubusercontent.com/u/4314092?v=4 |
OKですね。
まとめ
Adapterパターンは便利ですが、それ以上に「疎結合を意識したプログラミングを行う」ことが重要だと学べた気がします。
例えば、「func (r *Reader) ReadResource(re Resource) {}」メソッドですが、これのパラメータはインターフェースです。
1 2 3 |
type Resource interface { GetUser() UserInfo } |
構造体や特定の値ではなく、インターフェースを渡す事で、プログラミングを蘇結合にできています(と思います)。呼び出し側はこの関数を利用する時、GetUserメソッドを実装した構造体を渡すだけでよく、また、GetUserメソッドの実装内容を呼び出し側で自由に決められます^^
実際の現場では、実装後に機能のアップデートが頻繁にあります。そのため、柔軟に利用できるインターフェースの実装は必須ですね。
それでは、また!
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
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 { GetUser() UserInfo } // リソースを読み込むためのクラス type Reader struct{} // リソースからユーザー情報を読み込むメソッド // インターフェースを介して呼ばれる func (r *Reader) ReadResource(re Resource) { user := re.GetUser() fmt.Println(fmt.Sprintf("Name: %s, URL: %s", user.Name, user.AvatarURL)) } // HTTP Requestからリソースを読み込む type HttpRequest struct{} func (h *HttpRequest) GetUser() UserInfo { var u UserInfo resp, err := http.Get("https://api.github.com/users/golang") if err != nil { return u } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&u) if err != nil { return u } return u } // JSONファイルからリソースを読み込む // 同一ファイルに書いているが、これに機能追加ができないことにする type JSONRequets struct{} func (j *JSONRequets) ReadLocalJSON() UserInfo { raw, err := ioutil.ReadFile("./resource.json") if err != nil { fmt.Println(err.Error()) return UserInfo{} } var u UserInfo json.Unmarshal(raw, &u) return u } // Adapter(変換機) type JSONAdapter struct { jsonReader *JSONRequets } // GetUserメソッドを実装することで、ReadResourceメソッドに渡せる func (j JSONAdapter) GetUser() UserInfo { // ここからReadLocalJSONメソッドを呼び出す return j.jsonReader.ReadLocalJSON() } func main() { r := &Reader{} h := &HttpRequest{} fmt.Print("HTTP Request: ") r.ReadResource(h) j := &JSONRequets{} fmt.Print("JSON: ") r.ReadResource(&JSONAdapter{j}) } |
コメントを残す
コメントを投稿するにはログインしてください。