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

Go

こんにちは! 👋 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らしいコードを書く上で非常に重要な概念です。最初は少し難しく感じるかもしれませんが、実際にコードを書きながら使い方に慣れていきましょう! 💪

コメント

タイトルとURLをコピーしました