Go言語のインターフェースとジェネリクス(型パラメータ)の関係をわかりやすく解説
生徒
「先生、Go言語でいろんな型に対応できる便利な書き方ってありますか?」
先生
「はい、インターフェースとジェネリクス(型パラメータ)を組み合わせると、型に依存しない柔軟なコードを書けます。」
生徒
「インターフェースとジェネリクスはどう違うんですか?」
先生
「インターフェースは共通の操作(メソッド)を定義して型を抽象化する方法です。一方、ジェネリクスは関数や構造体を特定の型に依存せずに作るための機能です。」
生徒
「なるほど、両方を使うとさらに柔軟になるんですね!」
1. インターフェースとは?
Go言語のインターフェースは、異なる型でも同じ操作を保証するための仕組みです。例えば、犬も猫も「鳴く」操作がある場合、Speakというメソッドを共通に定義できます。
type Speaker interface {
Speak()
}
type Dog struct{}
type Cat struct{}
func (d Dog) Speak() { fmt.Println("ワンワン") }
func (c Cat) Speak() { fmt.Println("ニャー") }
DogもCatもSpeakerインターフェースとして扱えるので、同じ関数で処理可能です。
2. ジェネリクス(型パラメータ)とは?
ジェネリクスは、関数や構造体を特定の型に依存せずに作る方法です。型パラメータを使うことで、同じ処理を整数や文字列などさまざまな型で使えます。
func PrintValue[T any](value T) {
fmt.Println(value)
}
func main() {
PrintValue(100)
PrintValue("こんにちは")
}
ここで、Tは型パラメータで、anyはどんな型でも受け取れることを意味します。
3. インターフェースとジェネリクスの組み合わせ
ジェネリクスとインターフェースを組み合わせると、型に依存せずかつ特定の操作を保証する関数や構造体を作れます。
func MakeSpeak[T Speaker](a T) {
a.Speak()
}
func main() {
dog := Dog{}
cat := Cat{}
MakeSpeak(dog)
MakeSpeak(cat)
}
ここでは、TはSpeakerインターフェースを満たす型に限定されるため、必ずSpeakメソッドを持つ型だけが渡せます。
4. ジェネリクスを使った柔軟な構造体設計
構造体にジェネリクスを使うと、異なる型のデータを持つ構造体を同じ設計で作れます。例えば、異なる型のペットを格納できる構造体です。
type PetBox[T Speaker] struct {
Pet T
}
func (p PetBox[T]) ShowPet() {
p.Pet.Speak()
}
func main() {
dogBox := PetBox[Dog]{Pet: Dog{}}
catBox := PetBox[Cat]{Pet: Cat{}}
dogBox.ShowPet()
catBox.ShowPet()
}
このように、型に依存せず共通の操作を保証できるので、拡張性と安全性の高いコードになります。
5. インターフェースとジェネリクスの使い分け
- インターフェースは「操作の共通化」に向いている
- ジェネリクスは「型の柔軟性」に向いている
- 組み合わせると、型に依存せず操作も保証される安全なコードが書ける
- 新しい型を追加しても既存コードを変更せずに対応可能
Go言語ではインターフェースとジェネリクスを上手に使い分けることで、保守性と拡張性の高いプログラム設計が可能になります。