💭

Go らしい? interface の組み合わせでごにょごにょする

2025/01/24に公開

標準ライブラリの io パッケージ を見ていく。

interface

Read メソッドを必要とする Reader interface

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

Reader - io

組み合わせで表現する

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

Writer - io

type ReadWriter interface {
	Reader
	Writer
}

ReadWriter - io

  • 読み込みだけ必要な場合
  • 書き込みだけ必要な場合
  • 読み込み & 書き込みが必要な場合

それぞれ必要に応じて軽量な interface の組み合わせで最小限の表現をしている。

実装例

データを読み取って1行ずつの文字列に分割する関数

func lines(file os.File) []string {
    data, _ := io.ReadAll(dec.r)
    // ...
}

「ファイルを受け取って内容を1行ずつの文字列に分割」
※ io.ReadAll は Read 関数を呼び出して全てのデータを読み取ってバイト列([]byte)を返す。

⚠️ os.Fileio.ReadWriteSeekerio.Closer を実装しています。
さて、

  • Write 関数
  • Seek 関数
  • Close 関数
    必要でしょうか?

必要な実装だけの最小限の interface で受け取ると、

func lines(file io.Reader) []string {
    data, _ := io.ReadAll(dec.r)
    // ...
}
  • 👌 lines 関数はどう間違っても読み込み処理しか発生しない。 (リフレクションしない限りは)
  • ❤️ 関数の中身を読まなくても読み込み処理しか実行されないことがわかり安心

※ リフレクション自体 (の中でも特に unsafe な cast) はどうしても必要にならなければ使わない文化です。

サンプル (ちょっと実践的な例)

  • 保存、一覧の両方が必要
  • 一覧のみが必要

で interface を分ける。

type SaveLister interface {
	saver
	Lister
}

type saver interface {
	Save(g Group) error
}

type Lister interface {
	All() iter.Seq2[Group, error]
}

func NewLister(r Repository) Lister {
	return &saveLister{r}
}

func NewSaveLister(r Repository) SaveLister {
	return &saveLister{r}
}

type saveLister struct {
	// ...
}

Discussion