[Goのはじめ方] Part16: インターフェース(interface)の基本

こんにちは! Go言語の学習、順調に進んでいますか?今回はGoの強力な機能の一つであるインターフェース(interface)について学びます。インターフェースを理解すると、より柔軟で拡張性の高いコードを書けるようになりますよ!

1. インターフェースとは?

Goにおけるインターフェースは、メソッドのシグネチャ(名前、引数、戻り値の型)の集まりを定義したものです。具体的な実装(メソッドの中身)は持ちません。

インターフェースは、特定の振る舞い(どんなメソッドを持っているか)を定義するために使われます。「このインターフェースを満たす型は、これらのメソッドを必ず持っている」という契約のようなものです。これにより、異なる型であっても同じインターフェースを満たしていれば、同じように扱うことができます(これをポリモーフィズムと言います)。

ポイント: インターフェースは「何ができるか(What)」を定義し、それを実装する具体的な型が「どうやるか(How)」を定義します。

2. インターフェースの定義

インターフェースは type キーワードと interface キーワードを使って定義します。

type インターフェース名 interface { メソッド名1(引数型...) (戻り値型...) メソッド名2(引数型...) (戻り値型...) // ... 他のメソッドシグネチャ
} 

例として、Shape(図形)というインターフェースを考えてみましょう。図形には面積(Area)を計算する機能があるとします。

package main
import "fmt"
import "math"
// Shape インターフェース: 面積を計算する Area() メソッドを持つ
type Shape interface { Area() float64 // 面積を計算して float64 型で返すメソッド
}
// Circle 構造体 (例として)
type Circle struct { Radius float64
}
// Circle 型に Area() メソッドを実装
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius
}
// Rectangle 構造体 (例として)
type Rectangle struct { Width, Height float64
}
// Rectangle 型に Area() メソッドを実装
func (r Rectangle) Area() float64 { return r.Width * r.Height
}
func main() { // この時点ではインターフェースの定義のみ fmt.Println("インターフェース Shape が定義されました。")
} 

3. インターフェースの実装

Goのインターフェース実装は暗黙的(implicit)です。他の言語のように implements のようなキーワードで明示的に実装を宣言する必要はありません。

ある型が、インターフェースで定義されている全てのメソッドを(同じシグネチャで)持っていれば、その型はそのインターフェースを実装しているとみなされます。

先ほどの例では、Circle 構造体と Rectangle 構造体の両方が Area() float64 というメソッドを持っているので、どちらも自動的に Shape インターフェースを実装していることになります。

package main
import ( "fmt" "math"
)
// Shape インターフェース
type Shape interface { Area() float64
}
// Circle 構造体
type Circle struct { Radius float64
}
// Circle は Area() を持つので Shape を実装している
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius
}
// Rectangle 構造体
type Rectangle struct { Width, Height float64
}
// Rectangle は Area() を持つので Shape を実装している
func (r Rectangle) Area() float64 { return r.Width * r.Height
}
// インターフェースを使って面積を表示する関数
func PrintArea(s Shape) { // 引数に Shape インターフェース型を指定 fmt.Printf("この図形の面積は %.2f です。\n", s.Area())
}
func main() { c := Circle{Radius: 5} r := Rectangle{Width: 4, Height: 6} // Circle 型の変数 c は Shape インターフェースを満たすので PrintArea に渡せる PrintArea(c) // 出力: この図形の面積は 78.54 です。 // Rectangle 型の変数 r も Shape インターフェースを満たすので PrintArea に渡せる PrintArea(r) // 出力: この図形の面積は 24.00 です。 // Shape 型のスライスに異なる具体的な型を入れることも可能 shapes := []Shape{c, r} for _, shape := range shapes { fmt.Printf("スライス内の図形の面積: %.2f\n", shape.Area()) } // 出力: // スライス内の図形の面積: 78.54 // スライス内の図形の面積: 24.00
} 
すごい!: PrintArea 関数は具体的な型(CircleRectangle)を知らなくても、Shape インターフェースが保証する Area() メソッドを呼び出すだけで動作します。これがインターフェースの力です!

4. 空インターフェース (interface{}) 텅

メソッドを一つも持たないインターフェースは空インターフェース(empty interface)と呼ばれ、interface{} と書きます。(Go 1.18以降では、より分かりやすいエイリアスとして any が導入されましたが、interface{} も引き続き使えます。)

空インターフェースは、任意の型の値を保持することができます。なぜなら、どんな型でも「メソッドを0個持つ」という条件を満たすからです。

package main
import "fmt"
// 任意の型の値を表示する関数
func PrintAnything(val interface{}) { // any でも可: func PrintAnything(val any) fmt.Printf("値: %v, 型: %T\n", val, val)
}
func main() { var i interface{} // 空インターフェース型の変数 i = 42 PrintAnything(i) // 出力: 値: 42, 型: int i = "hello" PrintAnything(i) // 出力: 値: hello, 型: string i = true PrintAnything(i) // 出力: 値: true, 型: bool i = 3.14 PrintAnything(i) // 出力: 値: 3.14, 型: float64
} 

空インターフェースは非常に便利ですが、注意点もあります。空インターフェース型の変数から元の具体的な型の値を取り出して操作するには、型アサーション(Type Assertion) または 型スイッチ(Type Switch) が必要になります。これらは少し高度なトピックなので、別の機会に詳しく学びましょう。

注意: 空インターフェースは何でも入れられる反面、型情報が失われがちです。本当に必要な場面以外での多用は、コードの可読性や安全性を損なう可能性があるので避けましょう。

まとめ

今回はGoのインターフェースの基本について学びました。

  • インターフェースはメソッドシグネチャの集まりで、振る舞いを定義します。
  • 型のメソッドがインターフェースの全メソッドを実装していれば、暗黙的にそのインターフェースを実装したことになります。
  • インターフェースを使うことで、異なる型を共通の型として扱え、柔軟なコードが書けます(ポリモーフィズム)。
  • 空インターフェース interface{} (または any) は任意の型を保持できます。

インターフェースは、Goの標準パッケージ(例えば io.Readerfmt.Stringer など)でも広く使われており、Goらしいコードを書く上で非常に重要な概念です。最初は少し難しく感じるかもしれませんが、実際にコードを書きながら使い方に慣れていきましょう!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です