チュートリアル

Go基礎:インターフェース

Go入門インターフェース
広告エリア

インターフェースとは

メソッドのシグネチャの集合です。Goでは暗黙的に実装されます。

// インターフェースの定義
type Speaker interface {
    Speak() string
}

// 構造体
type Dog struct {
    Name string
}

// Speakerインターフェースを暗黙的に実装
func (d Dog) Speak() string {
    return d.Name + " がワンワン鳴いています"
}

type Cat struct {
    Name string
}

func (c Cat) Speak() string {
    return c.Name + " がニャーと鳴いています"
}

// インターフェースとして使用
func MakeSpeak(s Speaker) {
    fmt.Println(s.Speak())
}

dog := Dog{Name: "ポチ"}
cat := Cat{Name: "タマ"}

MakeSpeak(dog)  // "ポチ がワンワン鳴いています"
MakeSpeak(cat)  // "タマ がニャーと鳴いています"

複数メソッドのインターフェース

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

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}

type Circle struct {
    Radius float64
}

func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}

// ポリモーフィズム
func PrintShape(s Shape) {
    fmt.Printf("面積: %.2f, 周囲: %.2f\n", s.Area(), s.Perimeter())
}

PrintShape(Rectangle{10, 5})  // 面積: 50.00, 周囲: 30.00
PrintShape(Circle{7})         // 面積: 153.94, 周囲: 43.98

空インターフェース

あらゆる型を受け入れます。

// interface{} または any(Go 1.18以降)
func PrintAny(v any) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

PrintAny(42)
PrintAny("hello")
PrintAny(true)
PrintAny([]int{1, 2, 3})

型アサーション

var i interface{} = "hello"

// 型アサーション
s := i.(string)
fmt.Println(s)  // "hello"

// 安全な型アサーション
s, ok := i.(string)
if ok {
    fmt.Println("文字列:", s)
}

n, ok := i.(int)
if !ok {
    fmt.Println("intではありません")
}

型スイッチ

func describe(i interface{}) {
    switch v := i.(type) {
    case int:
        fmt.Printf("整数: %d\n", v)
    case string:
        fmt.Printf("文字列: %s\n", v)
    case bool:
        fmt.Printf("真偽値: %t\n", v)
    case []int:
        fmt.Printf("整数スライス: %v\n", v)
    default:
        fmt.Printf("不明な型: %T\n", v)
    }
}

インターフェースの合成

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

type Writer interface {
    Write(p []byte) (n int, err error)
}

// インターフェースを合成
type ReadWriter interface {
    Reader
    Writer
}

標準インターフェース

Stringer

type User struct {
    Name string
    Age  int
}

// fmt.Stringer を実装
func (u User) String() string {
    return fmt.Sprintf("%s (%d歳)", u.Name, u.Age)
}

user := User{Name: "太郎", Age: 25}
fmt.Println(user)  // "太郎 (25歳)"

error

type ValidationError struct {
    Field   string
    Message string
}

// error インターフェースを実装
func (e ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 {
        return ValidationError{
            Field:   "age",
            Message: "年齢は0以上である必要があります",
        }
    }
    return nil
}

io.Reader と io.Writer

import (
    "io"
    "strings"
)

// io.Readerを受け取る
func countBytes(r io.Reader) (int, error) {
    buf := make([]byte, 1024)
    total := 0
    for {
        n, err := r.Read(buf)
        total += n
        if err == io.EOF {
            break
        }
        if err != nil {
            return total, err
        }
    }
    return total, nil
}

// 使用
reader := strings.NewReader("Hello, World!")
count, _ := countBytes(reader)
fmt.Println(count)  // 13

sort.Interface

type Person struct {
    Name string
    Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

people := []Person{
    {"太郎", 30},
    {"花子", 25},
    {"次郎", 35},
}

sort.Sort(ByAge(people))
fmt.Println(people)  // 年齢順にソート

インターフェースのnil

type MyError struct {
    Msg string
}

func (e *MyError) Error() string {
    return e.Msg
}

func mayFail(fail bool) error {
    var err *MyError  // nil
    if fail {
        err = &MyError{Msg: "エラー"}
    }
    return err  // 注意: インターフェースはnilではない
}

// 正しい方法
func mayFailCorrect(fail bool) error {
    if fail {
        return &MyError{Msg: "エラー"}
    }
    return nil
}

実践例:ロガー

type Logger interface {
    Info(msg string)
    Error(msg string)
}

type ConsoleLogger struct{}

func (l ConsoleLogger) Info(msg string) {
    fmt.Println("[INFO]", msg)
}

func (l ConsoleLogger) Error(msg string) {
    fmt.Println("[ERROR]", msg)
}

type FileLogger struct {
    file *os.File
}

func NewFileLogger(path string) (*FileLogger, error) {
    f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        return nil, err
    }
    return &FileLogger{file: f}, nil
}

func (l *FileLogger) Info(msg string) {
    fmt.Fprintln(l.file, "[INFO]", msg)
}

func (l *FileLogger) Error(msg string) {
    fmt.Fprintln(l.file, "[ERROR]", msg)
}

// 使用(依存性注入)
type App struct {
    logger Logger
}

func (a *App) Run() {
    a.logger.Info("アプリケーションを開始します")
    // ...
    a.logger.Info("アプリケーションを終了します")
}

// 本番環境
app := &App{logger: ConsoleLogger{}}

// テスト用のモック
type MockLogger struct {
    Messages []string
}

func (m *MockLogger) Info(msg string)  { m.Messages = append(m.Messages, msg) }
func (m *MockLogger) Error(msg string) { m.Messages = append(m.Messages, msg) }

まとめ

  • インターフェースはメソッドシグネチャの集合
  • 実装は暗黙的(implementsキーワード不要)
  • interface{}/anyはあらゆる型を受け入れる
  • 型アサーションで具体的な型を取得
  • 型スイッチで型による分岐
  • インターフェースを合成して大きなインターフェースを作成
  • Stringer, error, io.Readerなどの標準インターフェース

これでGo基礎は完了です。次は実践編でファイル操作やエラー処理を学びましょう。

広告エリア