0Pricing
Go Academy · レッスン

インターフェースの定義と実装

暗黙的な実装と空のインターフェース

「インターフェースの定義と実装」はCoddyKit上の無料Go Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはGo Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Go Academyコースには全4レッスンが含まれています。

インターフェースとは

Goのインターフェースは、メソッドシグネチャの集合です。それらすべてのメソッドを実装する型は、implementsキーワードなしで暗黙的にインターフェースを満たします。これは構造的型付け、またはダックタイピングと呼ばれます。

インターフェースの宣言

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

package main
import "fmt"

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }
func (c Circle) Area() float64      { return 3.14159 * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius }

func main() {
    var s Shape = Circle{5}
    fmt.Printf("Area: %.2f\n", s.Area())
}

暗黙的にインターフェースを満たす

意図を宣言する必要はありません。型が適切なメソッドを持っていれば、自動的にインターフェースを満たします:

package main
import "fmt"

type Greeter interface{ Greet() string }

type English struct{}
func (e English) Greet() string { return "Hello" }

type Spanish struct{}
func (s Spanish) Greet() string { return "Hola" }

func say(g Greeter) { fmt.Println(g.Greet()) }

func main() {
    say(English{})
    say(Spanish{})
}

空のインターフェース

interface{}(またはGo 1.18以降のany)は、すべての型によって満たされます。コンパイル時に型が不明な場合に使われます:

package main
import "fmt"

func printAny(v any) { fmt.Printf("%T: %v\n", v, v) }

func main() {
    printAny(42)
    printAny("hello")
    printAny([]int{1,2,3})
}

インターフェースの値

インターフェースの値は、具体的な型と具体的な値の2つを保持します。nilインターフェースでは、どちらもnilに設定されています:

package main
import "fmt"

type Animal interface{ Sound() string }
type Dog struct{}
func (d Dog) Sound() string { return "woof" }

func main() {
    var a Animal
    fmt.Println(a == nil) // true
    a = Dog{}
    fmt.Println(a == nil) // false
    fmt.Println(a.Sound())
}

複数のインターフェースを満たす

1つの型で複数のインターフェースを同時に満たせます:

package main
import "fmt"

type Reader interface{ Read() string }
type Writer interface{ Write(string) }
type ReadWriter interface{ Reader; Writer }

type File struct{ content string }
func (f *File) Read() string       { return f.content }
func (f *File) Write(s string)     { f.content = s }

func main() {
    var rw ReadWriter = &File{}
    rw.Write("hello")
    fmt.Println(rw.Read())
}

インターフェースの代入

より広いインターフェースには、より限定的な具体値を保持できます。インターフェース型の間で代入するには、具体的な型が両方のインターフェースを満たしている必要があります:

package main
import "fmt"

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) }

func main() {
    var s Stringer = Point{3, 4}
    fmt.Println(s.String())
}

nilインターフェースの落とし穴

nilの具体値を保持するnilではないインターフェースは、nilと等しくありません:

package main
import "fmt"

type MyErr struct{}
func (e *MyErr) Error() string { return "oops" }

func bad() error {
    var e *MyErr // nil pointer
    return e     // returns non-nil interface!
}

func main() {
    err := bad()
    fmt.Println(err == nil) // false — common gotcha
}

インターフェースでテスト可能性を高める

依存関係をインターフェースとして定義すると、テストで実際の実装をモックに置き換えられます:

package main
import "fmt"

type EmailSender interface{ Send(to, body string) error }

type MockSender struct{ Sent []string }
func (m *MockSender) Send(to, body string) error {
    m.Sent = append(m.Sent, to)
    return nil
}

func notify(s EmailSender, addr string) { s.Send(addr, "hello") }

func main() {
    mock := &MockSender{}
    notify(mock, "a@b.com")
    fmt.Println(mock.Sent) // [a@b.com]
}

インターフェースを受け取り、構造体を返す

Goのベストプラクティスの1つは、関数パラメーターにはインターフェース(柔軟性のため)を使い、戻り値の型には具体的な型(呼び出し元が受け取るものを明確にするため)を使うことです:

package main

type Logger interface{ Log(string) }

type FileLogger struct{}
func (f *FileLogger) Log(s string) {}

func NewFileLogger() *FileLogger { // returns concrete
    return &FileLogger{}
}

func process(l Logger) {} // accepts interface

func main() { process(NewFileLogger()) }

インターフェースは小さく保つ

Goの標準ライブラリでは、小さなインターフェースが重視されています。io.Readerはメソッドを1つ、io.Writerもメソッドを1つ持ちます。小さなインターフェースは組み合わせやすく、モックも作りやすくなります。

確認問題

型は、インターフェースを実装することを宣言する必要がありますか?

まとめ: インターフェース

重要なポイント:

  • インターフェースはデータではなく振る舞いを定義します
  • インターフェースの充足は暗黙的で、implementsキーワードはありません
  • 空のインターフェース(any)はすべての型を受け入れます
  • nilインターフェースの落とし穴に注意します
  • インターフェースは小さく、目的を絞って保ちます

練習課題

Save(key, value string) errorとLoad(key string) (string, error)を持つStorageインターフェースを定義してください。マップを基盤とするMemStorageで実装し、テストしてください。

よくある質問

「インターフェースの定義と実装」レッスンは無料ですか?

はい。「インターフェースの定義と実装」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Go Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Go Academyコースには全4レッスンが含まれています。

「インターフェースの定義と実装」で何を学びますか?

暗黙的な実装と空のインターフェース ブラウザで直接実行するハンズオンコードでGo Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Go Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのGo Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「インターフェースの定義と実装」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このGo Academyレッスンでコードを書いて実行できますか?

はい。すべてのGo Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. インターフェースの定義と実装
  2. 標準ライブラリの主要インターフェース
  3. 型アサーションと型switch
  4. インターフェースのコンポジションとベストプラクティス
← Go Academyに戻る