コンテンツにスキップ

Go/interface

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

Goのinterfaceキーワードは、メソッドの集合を定義するための抽象型を宣言するために使用される予約語です。インターフェースは、Go言語のポリモーフィズムを実現する主要な仕組みであり、型の振る舞いに焦点を当てた設計を可能にします。

1. 基本的なインターフェースの宣言

[編集]
type インターフェース名 interface {
    メソッド1(引数リスト) 戻り値リスト
    メソッド2(引数リスト) 戻り値リスト
    // ...
}
type Writer interface {
    Write([]byte) (int, error)
}

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

[編集]

Goのインターフェースは暗黙的に実装されます。ある型が特定のインターフェースのすべてのメソッドを実装していれば、その型はそのインターフェースを満たします(明示的な宣言は不要)。

インターフェースの宣言

[編集]
type Stringer interface {
    String() string
}

インターフェースの実装

[編集]
type Person struct {
    Name string
    Age  int
}

// Personは暗黙的にStringerインターフェースを実装
func (p Person) String() string {
    return fmt.Sprintf("%s, %d years old", p.Name, p.Age)
}

3. 標準的なインターフェースと実装の比較

[編集]

fmt.Stringerインターフェース

[編集]
// 標準ライブラリの定義
type Stringer interface {
    String() string
}

値レシーバーによる実装

[編集]
type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

ポインタレシーバーによる実装

[編集]
type Counter struct {
    count int
}

func (c *Counter) String() string {
    return fmt.Sprintf("Count: %d", c.count)
}
// *Counter型はStringerを満たすが、Counter型は満たさない

4. 空インターフェース

[編集]

Go 1.18より前は、空インターフェース(interface{})は任意の型の値を保持できる汎用型として使用されていました。Go 1.18以降はanyがその別名として導入されています。

Go 1.18より前

[編集]
func PrintAnything(v interface{}) {
    fmt.Println(v)
}

Go 1.18以降

[編集]
func PrintAnything(v any) {
    fmt.Println(v)
}

5. インターフェースの埋め込み

[編集]

インターフェースは他のインターフェースを埋め込むことで合成できます。

単一インターフェース

[編集]
type Reader interface {
    Read(p []byte) (n int, err error)
}

合成インターフェース

[編集]
type ReadWriter interface {
    Reader
    Writer
}
// ReadWriterは以下と同等:
// type ReadWriter interface {
//     Read(p []byte) (n int, err error)
//     Write(p []byte) (n int, err error)
// }

6. 型アサーションと型スイッチ

[編集]

型アサーション

[編集]
// 値と成功/失敗を返す形式
value, ok := someInterface.(ConcreteType)
if !ok {
    // 変換失敗の処理
}

// 直接アサーションする形式(失敗時はパニックを引き起こす)
value := someInterface.(ConcreteType)

型スイッチ

[編集]
switch v := someInterface.(type) {
case string:
    fmt.Println("文字列:", v)
case int:
    fmt.Println("整数:", v)
case bool:
    fmt.Println("真偽値:", v)
default:
    fmt.Println("その他の型:", v)
}

7. ジェネリクスとインターフェース型制約(Go 1.18以降)

[編集]

Go 1.18からは、インターフェースを型パラメータの制約として使用できます。

基本的な型制約

[編集]
// ~Tは基底型がTの全ての型を意味する
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
    ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
    ~float32 | ~float64
}

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

メソッド型制約とユニオン型制約の比較

[編集]
// メソッド型制約(従来のインターフェース)
type Stringer interface {
    String() string
}

// ユニオン型制約(Go 1.18以降)
type IntOrString interface {
    int | string
}

// 両方を組み合わせた型制約
type StringableNumber interface {
    ~int | ~int64 | ~float64
    String() string
}

8. インターフェース値の内部構造

[編集]

インターフェース値は内部的に、型情報と実際の値(またはポインタ)の2つの部分から構成されています:

インターフェース値 = (具体的な型の情報, 具体的な値へのポインタ)

nilインターフェースとnilを保持するインターフェース

[編集]
// nilインターフェース
var w io.Writer
// w == nil は true

// nilを保持するインターフェース
var p *bytes.Buffer
w = p  // pはnilだが、wはnilではない
// w == nil は false(型情報があるため)

9. インターフェースのベストプラクティス

[編集]

インターフェースは使用側で定義する

[編集]
// strings パッケージは bytes.Buffer を使うのではなく、
// io.Reader や io.Writer インターフェースを受け取る
func NewReader(r io.Reader) *Reader

小さいインターフェースが良い

[編集]
// 大きすぎるインターフェース
type FileSystem interface {
    Open(name string) (File, error)
    Create(name string) (File, error)
    Remove(name string) error
    // ...多数のメソッド
}

// 小さいインターフェース
type Opener interface {
    Open(name string) (File, error)
}

10. インターフェースの特記事項

[編集]
  1. インターフェースはGoのポリモーフィズムを実現する主要な仕組みです。
  2. 暗黙的な実装により、既存のコードを変更せずに新しい抽象化を導入できます。
  3. インターフェースはコードの疎結合性を高め、テスタビリティを向上させます。
  4. Go 1.18以降、インターフェースは型集合の仕様としても機能し、ジェネリクスの型制約として使用できます。
  5. 慣例として、単一メソッドのインターフェース名は、そのメソッド名に「-er」を付けることが多いです(Reader, Writer, Closerなど)。

Goのinterfaceキーワードは、型の振る舞いに基づいた柔軟な抽象化を提供し、特にGo 1.18以降のジェネリクス機能の追加により、より強力な型システムの一部となっています。