こんにちは。KOUKIです。
とある企業でWeb系のエンジニアをしています。開発言語はGo言語です。
「Go言語によるInterfaceのオススメの使い方」について紹介したいと思います。
「Interfaceは名前だけ聞いたことあるけど、あんま使わないな」と思ったそこのあなた!一つの参考例をご紹介できると思います。
Interfaceの基礎については、以下の記事を参考にしてください!
<目次>
Interfaceでアクセスレベルを調整する
私がオススメするInterfaceの使い方として、「アクセスレベルの調整」をご紹介したいと思います。
例えば、以下のHttpClientコードを例に考えてみましょう。
1 2 3 4 5 6 7 |
package gohttp type HttpClient struct{} func (c *HttpClient) Get() {} func (c *HttpClient) Post() {} |
HttpClientのStructを用意しました。そしてメソッドとして、GetとPostを実装しています。
このメソッドを使う側は、以下のようにメソッドを呼び出すでしょう。
1 2 3 4 5 6 7 8 9 10 11 |
package main import ( "interface/gohttp" ) func main() { client := gohttp.HttpClient{} client.Get() client.Post() } |
gohttp.HttpClient{}にて、先ほど実装したHttpClientをclient変数に格納しています。
そして、その変数からGetやPostにアクセスしています。このコードは、別に悪くはありません。一般的な実装とも言えます。
しかし、問題点をあげるとすれば、client変数を通じて実装者が意図しないパラメータやメソッドにアクセスできてしまう可能性があります。
例えば、以下のようにです。
1 2 3 4 5 6 |
package gohttp type HttpClient struct { SecretPassword string // SecretPasswordを用意 } ... |
SecretPasswordをHttpClientに定義しました。Secretとついているので、実装者は使う側にこの変数にアクセスして欲しくなかったはずですが、実際にはアクセスできてしまいます。
1 2 3 4 |
func main() { client := gohttp.HttpClient{} fmt.Println(client.SecretPassword) // SecretPassword } |
変数をプライベートにすればいいんじゃない?などの意見があるかもしれませんが、ここで重要なのは「実装者がアクセスして欲しい対象にのみアクセスできる窓口(Interface)」を用意することなのです。
そうすることで、より意図が明確なコードが書けます。
具体例をあげましょう。例えば、GetとPostのみにアクセスさせたい場合は、次のようにInterfaceを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package gohttp type httpClient struct { SecretPassword string } // Interfaceを返却するコンストラクタを定義 func New() HttpClient { client := httpClient{SecretPassword: "hoge"} return &client } // アクセスを許可したいメソッドを定義する type HttpClient interface { Get() Post() } func (c *httpClient) Get() {} func (c *httpClient) Post() {} |
GetやPostを内包するHttpClientのInterfaceを定義しました。そして、httpClientのstructを改めて定義し、New関数でhttpClientを返しています。
ここでNew関数の戻り値に注目して欲しいのですが、HttpClientのInterfaceを返却しています!
Go言語ではInterfaceに定義された関数(ここでは、GetやPost)をメソッドとしてStructが定義した場合は、Interfaceを暗黙的に実装したことになるんです。
httpClient Structは、GetとPostのどちらも実装しているので、Interfaceの要件を満たしています。そのため、New関数の戻り値として、httpStructを指定できるようになっています。
裏を返すと、GetやPostが実装されていないとInterfaceの要件が満たされず、エラーが発生するという訳です。
試しに、Postをコメントアウトしてみましょう。

HttpClientのInterfaceを満たせていないので、エラーになっていますね。
1 2 |
cannot use &client (type *httpClient) as type HttpClient in return argument: *httpClient does not implement HttpClient (missing Post method)go |
そして、このように実装すると実装者の意図が明確になります。開発者は、New関数でHttpClientのInterfaceを返しているのをみて、「ああ、GetとPostにしかアクセスさせたくないんだなぁ」と読めるのです!
実際に、先ほど実装したInterfaceを使ってみましょう。
IDEの力を借りると、アクセスできるプロパティやメソッドを確認することができます。

gohttpパッケージでは、外部に公開することができるNew関数のみしかアクセスできません。HttpClientはInterfaceですし、httpClientはプライベートで定義しているためです(Go言語では、先頭文字を大文字にするとパブリック、小文字にするとプライベート扱いになる)。
そして、Newは、HttpClient Interfaceを返すため、GetやPostのみにアクセスできます。
つまり、アクセスレベルを絞ることができたということです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package main import ( "interface/gohttp" ) func main() { // NewではInterfaceが帰ってくる client := gohttp.New() client.Get() client.Post() // アクセスが許可されていない // fmt.Println(client.SecretPassword) } |
これは本当によく使うパターンなので覚えておいてそんはありません!
おわりに
本記事で紹介したInterfaceの使い方は、プログラミングの利便性をあげる一つの参考例になると思います。
他にもMockや依存性逆転の原則なんかでも活躍するので、いずれご紹介できればなと思います。
それでは、また!
最近のコメント