Go言語 ~基礎編~ Interface

go

今回は、Interfaceについて学びましょう。

学習記事まとめ

序章

KOUKI
KOUKI

Interfaceに触れる前に、少しお付き合いください。

次のディレクトリとファイルを用意してください。

tape.goにテーププレイヤーとテープレコーダーのStructとそれぞれのメソッドを実装してみます。

テーププレイヤーとテープレコードの挙動は、Record関数以外は同じです。

続いて、TapePlayerの挙動を確認するため、main関数を実装します。

main関数にてTapePlayerのStructを宣言し、mixtape(音楽リスト)と共にplayList関数へ引数として渡します。

問題なく動きましたね。

今度は、TapeRecorderの挙動を確認してみましょう。

これはコンパイルエラーになります。playListの第一引数がTapePlayerだからです。

どちらも似たような実装なので、区別なく動いて欲しいです。

Go言語にはこの問題を解決するために、Interfaceが用意されています。

Interfaceとは

Interfaceは、JavaやC#などの静的型付け言語に登場する概念で、Go言語にも存在します。

Interfaceは、特定の処理を持つことを期待されたメソッドの塊です。

Interfaceを定義するには、interfaceキーワードを使用します。

Go言語のインターフェースの実装は、結構簡単です。

サンプルコードを以下に記載します。

interfaceを実装するには、他の言語では明示的に指定する必要がありますが、Go言語ではメソッドとして全て定義してしまえば、自動的にinterfaceを実装した(要件を満たした)と見なされます

上記の例では、MyTypeにinterfaceを実装しています。

呼び出し方も覚えてしまいましょう。main関数を実装します。

まず、interface typeの変数を宣言し、そこにinterfaceのメソッドを全て実装したMyTypeを代入すればOKです。

プログラムを実行してみましょう。

Interfaceへ格納できる値

前述のように、interface typeを伴う変数を宣言した場合、この変数にはinterfaceの要件を満たすどんな値も格納可能になります。

例えば、次のサンプルコードを見てください。

このサンプルコードには、NosieMaker interfaceのメソッド(MakeSound)を継承した変数「Whistle」および「Horn」を用意しました。

注目して欲しいのが、main関数内の変数の割り当てです。

interface typeと共に宣言したtoy変数にWhistle、Hornの両方の値を格納できています。

これは、どちらもNoiseMaker interfaceのMakeSoundメソッドを継承しているからです。

さらに一歩、理解を深めましょう。

実は、interfaceをパラメータにした関数を定義できます。

この場合もinterfaceの要件を満たせていれば、playの引数には何を渡しても問題ありません。

一見便利そうですが、注意点もあります。

前述の通り、interface typeの変数からinterfaceに定義したメソッドの呼び出しが可能です。

先ほどのサンプルコードに新しくRobot typeを宣言して、MakeSoundメソッドの他にWalkメソッドも定義してみましょう。

Walkメソッドは、新しく作成したRobotにしかありません。

このWalkメソッドをplay関数に追加したところコンパイルエラーになってしまいました。

interfaceの要件を満たさない値をplay関数に追加することはできないようです。

やってみよう

では、序章のプログラムを修正してみましょう。

main関数にinterfaceを実装して、playListの第一引数に渡しました。

プログラムを実行してみましょう。

Type assertions

サンプルコードのTapeRecorderには、Recordメソッドが存在します。これは、TapePlayerには存在しないメソッドです。

前述の通り、interfaceの要件を満たしていないメソッドをinterface typeを引数にした関数内に定義するとエラーになります。

このエラーを回避するにはどうすればよいのでしょうか?

最初に考えられる回避策は、型変換でしょうか。

PlayerをTapeRecorderに型変換しようとしました。しかし、interface typeの型変換はできないため、エラーになります。

このエラー上に、”type assertion“が出てきました。

type assertionは、日本語でいうと「型推論」です。次のように実装するとエラーを回避することができます。

上記の例では、interface typeで宣言したp変数で、TapeRecorderをtype assertionしています。このようにすれば、interfaceに定義されていないメソッドであっても呼び出し可能です。

しかし、この実装は別のバグを発生させます。

例えば、TryOut関数にTapePlayerを渡してみましょう。

このように、type assertionしたinterface typeとは別のinterface typeを渡すとpanicが発生します。

このエラーを回避するには、Go言語では次のように実装します。

type assertionの2番目の引数にて、bool値を取得することができます。

type assertionに成功した場合はtrueをそれ以外はfalseを返すので、これをok変数に格納することで、panicを回避します。

Error Inteface

これまで、Go言語でエラーを出力するときは、次のように実装していました。

Go言語では、error interfaceが次のように定義されています。

error interfaceの要件を満たすには、Error関数とstring typeの戻り値をもつメソッドを定義すればOKです。

サンプルコードを示します。

GetTitleErrorはerror interfaceの要件を満たしたので、error interfaceをtypeとして持つ変数に格納できます。

より詳細な情報を持たせたい場合は、次のように実装します。

error interfaceの要件を満たしたこのメソッドを次のように利用することができます。

カスタムエラーを作っているような感覚ですね。

The Stringer Interface

Go言語のfmtパッケージには、Stringer Interfaceが組み込まれています。

Stringer Interfaceは、String関数とstringの戻り値を保持しています。

例えば、こんな使い方ができます。

自分でカスタマイズした文字列を自由に表示できるので、結構便利なやつです。

先ほどのサンプルでは、「bookTitle.String()」と指定しましたが、次のような書き方でもOKです。

色々と応用ができそうです。

The empty interface

fmt.Printlnは、大変便利ですよね。様々なtypeを受け取り、コンソール上に出力できるのですから。

Printlnの引数を見てみると「…interface{}」を持っています。これは一体なんなのでしょうか?

「…」は、以前学習した可変長引数ですね。これを設定しておくと、呼び出し先で受け取れる引数の数に制限が無くなります。

interfaceを宣言するには、interfaceのメソッドを全て実装したDefined Typeを用意する必要があります。

例えば、次のinterfaceが存在していた場合、MakeBookをメソッドとして定義したDefined Typeが必要です。

しかし、メソッドを定義していないinterfaceを宣言した場合は、どうなるのでしょうか?例えば、こんな感じのやつです。

このinterfaceはメソッドを実装していないため、「empty interface」と呼ばれています。

このempty interfaceは、任意の値を受け取ることができます。何の値も持っていないからどのような値も受け入れることができる、寛大なinterfaceです。

つまり、こんな感じの使い方が可能です。

empty interfaceは、メソッドの実装も求められない代わりに、任意のtypeの値を受け取ることができるのです。

しかし、便利だからといって全ての関数にempty intefaceを実装するのはよくありません。

次のサンプルコードを見てください。

前述の通り、どのようなtypeの値でも受け取ることができていますね。

しかし、empty methodは、メソッドを保有していないため、メソッドを呼ぶことはできません。

この場合は、type assertionをする必要があります。

type assertionを行うことでエラーを回避することは可能ですが、通常は次のように実装します。

まとめ

KOUKI
KOUKI

最後にこの章で学んだことをまとめます。

Interface まとめ
・ interfaceには、期待された値を持つメソッドを定義する
・ interfaceの要件を満たすには、interfaceのメソッドを全て実装する必要がある
・ interfaceはabstract typeであり、メソッドの処理を書くことはできない
・ Go言語ではinterfaceの要件が満たされたかどうかは自動的に判断される
・ interface typeを持つ変数はinterfaceに定義されているメソッドしか呼べない
・ しかし、type assertionを使用すれば、個別に実装したメソッドを呼ぶことができる
・ type assertionは、2番目の戻り値に型変換が成功したか否かを表すbool値を返す

次回

次は、エラーハンドリングについて学びましょう。

参考書籍

コメントを残す