0Pricing
Go Academy · レッスン

インターフェースのコンポジションとベストプラクティス

小さなインターフェースと依存性逆転

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

インターフェースの合成

Go では、インターフェースにほかのインターフェースを埋め込んで、より大きなインターフェースを合成できます:

package main
import "io"

// io.ReadWriter composes Reader and Writer
type ReadWriter interface {
    io.Reader
    io.Writer
}

// Your own composition:
type Processor interface {
    io.Reader
    Process() error
    io.Closer
}

func main() { _ = (*Processor)(nil) }

小さなインターフェースの原則

最も強力な Go のインターフェースは、メソッドを 1 つか 2 つだけ持ちます。これにより、それを満たせる型の数を最大化できます:

  • io.Reader — メソッド 1 つ
  • io.Writer — メソッド 1 つ
  • error — メソッド 1 つ
  • fmt.Stringer — メソッド 1 つ

大きなインターフェースは、モックやテストが難しくなります。

依存性逆転

関数のパラメーターには具体型ではなくインターフェースを受け取るようにします。これにより、コードを特定の実装から切り離せます:

package main
import "fmt"

type Store interface {
    Get(key string) (string, bool)
    Set(key, val string)
}

func cache(s Store, key, value string) {
    if _, ok := s.Get(key); !ok {
        s.Set(key, value)
    }
}

type MapStore struct{ m map[string]string }
func (ms *MapStore) Get(k string) (string, bool) { v, ok := ms.m[k]; return v, ok }
func (ms *MapStore) Set(k, v string) { ms.m[k] = v }

func main() {
    s := &MapStore{m: map[string]string{}}
    cache(s, "x", "hello")
    v, _ := s.Get("x")
    fmt.Println(v)
}

インターフェース分離

大きなインターフェースを、より小さく目的を絞ったインターフェースに分割します。利用側は、使用するメソッドにのみ依存します:

package main

// Bad: large interface forces all methods
type BigStorage interface {
    Read(key string) string
    Write(key, val string)
    Delete(key string)
    List() []string
    Backup() error
}

// Good: small, focused interfaces
type Reader interface{ Read(key string) string }
type Writer interface{ Write(key, val string) }
type Lister interface{ List() []string }

func readAll(r Reader) {} // only needs Read

func main() { _ = readAll }

具体型を返す

コンストラクターはインターフェースではなく具体型を返すようにします。呼び出し側では、具体型をいつでもインターフェース変数に代入できます:

package main

type Logger interface{ Log(string) }
type ConsoleLogger struct{}
func (c *ConsoleLogger) Log(s string) {}

// Good: return concrete *ConsoleLogger
func NewConsoleLogger() *ConsoleLogger { return &ConsoleLogger{} }

// Avoid: return Logger (hides the concrete type)
// func NewConsoleLogger() Logger { return &ConsoleLogger{} }

func main() { _ = NewConsoleLogger() }

インターフェースの乱用

「念のため」という理由で、すべての型にインターフェースを作成しないでください。次の場合にのみインターフェースを作成します:

  • 現在または近い将来に複数の実装がある
  • テストで依存関係をモックする必要がある
  • ライブラリを作成しており、呼び出し側との依存関係を切り離したい

拡張のためのインターフェース埋め込み

インターフェースを埋め込むことで、デフォルト実装を持つオプションのメソッドを追加します:

package main
import "fmt"

type Handler interface{ Handle(msg string) }
type NamedHandler interface {
    Handler
    Name() string
}

func dispatch(h Handler) {
    if nh, ok := h.(NamedHandler); ok {
        fmt.Printf("[%s] ", nh.Name())
    }
    h.Handle("event")
}

type MyHandler struct{}
func (m MyHandler) Handle(msg string)  { fmt.Println(msg) }
func (m MyHandler) Name() string       { return "my" }

func main() { dispatch(MyHandler{}) }

インターフェースを使ったモック

依存関係をインターフェースとして定義し、テストではモックを注入します:

package main
import "fmt"

type Clock interface{ Now() int64 }

type MockClock struct{ T int64 }
func (m MockClock) Now() int64 { return m.T }

func isExpired(c Clock, expiresAt int64) bool {
    return c.Now() > expiresAt
}

func main() {
    mc := MockClock{T: 1000}
    fmt.Println(isExpired(mc, 500))  // true
    fmt.Println(isExpired(mc, 1500)) // false
}

再利用におけるインターフェース埋め込みと構造体埋め込みの違い

API の合成にはインターフェース埋め込みを、コードの再利用には構造体埋め込みを使用します。目的が異なるため、混同しないでください。

コンパイル時のインターフェース準拠の検出

空白識別子を使ったコンパイル時チェックにより、型がインターフェースを満たしていることを検証します:

package main

type Doer interface{ Do() }
type MyType struct{}
func (m *MyType) Do() {}

// Compile-time check — no runtime cost
var _ Doer = (*MyType)(nil)

func main() {}

確認問題

Go で新しいインターフェースを定義するのはどのようなときですか?

まとめ:インターフェースのベストプラクティス

重要なポイント:

  • インターフェースは小さく保ちます — メソッドは 1 つか 2 つ
  • 小さなインターフェースからインターフェースを合成します
  • インターフェースを受け取り、具体型を返します
  • テスト容易性や複数の実装が必要な場合にインターフェースを作成します
  • 空白識別子への代入によるコンパイル時チェックを使用します

練習課題

Notify(msg string) error を持つ Notifier インターフェースを設計してください。EmailNotifier と SMSNotifier に実装し、[]Notifier に送信する broadcast 関数を作成してください。

よくある質問

「インターフェースのコンポジションとベストプラクティス」レッスンは無料ですか?

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

「インターフェースのコンポジションとベストプラクティス」で何を学びますか?

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

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

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

「インターフェースのコンポジションとベストプラクティス」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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