インターフェースのコンポジションとベストプラクティス
小さなインターフェースと依存性逆転
「インターフェースのコンポジションとベストプラクティス」は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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- インターフェースの定義と実装
- 標準ライブラリの主要インターフェース
- 型アサーションと型switch
- インターフェースのコンポジションとベストプラクティス