https://go.dev/wiki/CodeReviewComments#interfaces
Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values. The implementing package should return concrete (usually pointer or struct) types: that way, new methods can be added to implementations without requiring extensive refactoring.
一言でいうと: 「何が必要か」は、必要としている側が書く。
- 求人票(interface)は、雇う側(使う側)が書く。応募者(実装側)は求人票を見ずに、ただスキル(メソッド)を持っているだけ
- 採用判定はコンパイラがする。スキルが揃っていれば採用(= 引数に渡せる)
なぜ使う側に置けるのか
Go には implements が無いから。
- Java:
class DB implements Storeと実装側が名乗る → 実装側がStoreを知っている必要がある - Go: 誰も名乗らない。渡した瞬間に、コンパイラが「必要なメソッドが全部あるか」を見比べる
- → 実装側は
Storeを知らなくていい →Storeを使う側に置ける
- → 実装側は
https://go.dev/doc/effective_go#blank_implements
例
user/user.go(使う側。import無し)
package user
// user が必要な分だけ書く
type Store interface {
Get(id string) (string, error)
}
func Greet(s Store, id string) (string, error) {
name, err := s.Get(id)
if err != nil {
return "", err
}
return "Hello, " + name, nil
}
postgres/postgres.go(実装側。import無し。Store という文字が出てこない)
package postgres
type DB struct{}
// interface ではなく具象型を返す
func New() *DB { return &DB{} }
func (*DB) Get(id string) (string, error) { return "alice", nil } // 値はダミー
main.go(両方を知っているのはここだけ)
db := postgres.New()
msg, _ := user.Greet(db, "1") // *postgres.DB は Get を持つので Store として渡せる
fmt.Println(msg) // Hello, alice
Get を消すと、main.go の渡した行で怒られる(= ここで判定している証拠)。
*postgres.DB does not implement user.Store (missing method Get)
うれしいこと
userがpostgresをimportしない(DIP: 業務ロジックがDBの都合に依存しない)
普通: user ──> postgres
この形: main ──> user, main ──> postgres
- interfaceが小さい(
Get1つ)ので、テストの偽物がすぐ作れる
type fakeStore struct{}
func (fakeStore) Get(id string) (string, error) { return "bob", nil }
// Greet(fakeStore{}, "1") → "Hello, bob"。DB不要
- 実装側が具象型を返すので、
DBにメソッドを足しても誰も壊れない- interfaceを返していたら、メソッド追加 = interface変更 = それを満たしていた偽物が全部コンパイルエラー
逆に、実装側に置くと
userがpostgres.Storeのためにpostgresをimportする → MySQLに替えるとuserも書き換え- 「DBができること全部入り」の大きいinterfaceになりがち。使う側は
Getしか要らないのに