7.2.4. Interfaces
Interfaces in Go sind klein, implizit und gehören dorthin, wo Verhalten gebraucht wird – nicht dorthin, wo es implementiert wird.
Oft reicht eine Methode. Vorbilder in der Stdlib: io.Reader, io.Writer, fmt.Stringer. Proverbs: The bigger the interface, the weaker the abstraction und interface{} says nothing – dafür schreibt man heute any.
type Reader interface {
Read(p []byte) (n int, err error)
}
Ein-Methoden-Interfaces heißen oft auf -er: Reader, Writer. Berühmte Methodennamen nicht mit anderer Signatur wiederverwenden.
Das Package, das Verhalten nutzt, besitzt die Interface-Deklaration. Der Produzent liefert konkrete Typen. Keine Interfaces „zum Mocken“ auf der Produzentenseite und keine Abstraktion, bevor ein realistischer zweiter Nutzer existiert.
// package consumer
type Thinger interface{ Thing() bool }
func Foo(t Thinger) string { /* ... */ }
// package producer – konkret zurückgeben
type Thinger struct{ /* ... */ }
func NewThinger() Thinger { return Thinger{} }
Parameter dürfen Interfaces sein, die das benötigte Verhalten beschreiben. Rückgaben sind meist konkrete Structs/Pointer (error und bewusste Factories ausgenommen).
func Save(w io.Writer, data []byte) error {
_, err := w.Write(data)
return err
}
func NewClient(addr string) *Client { return &Client{addr: addr} }
Konkrete Rückgaben geben dem Caller die volle API; er kann den Wert trotzdem an alles übergeben, was ein kleineres Interface braucht.
Es gibt kein implements. Ein Typ erfüllt ein Interface, wenn er die Methoden hat. Optionaler Compile-Check (Uber-Stil):
var _ http.Handler = (*Handler)(nil)
Interface-Werte tragen Typinfo und Datenpointer selbst. *io.Reader als Parameter ist fast immer falsch – io.Reader reicht.
Pointer-Receiver-Methoden gehören nur zu *T; Value-Receiver zu T und *T. Braucht man Mutation über Pointer-Methoden, Pointer speichern bzw. zurückgeben, wenn der Wert einem Interface zugewiesen wird.
Mehr zu vorzeitiger Abstraktion: 7.2.7. Einfachheit.
Diese Seite gehört zu 7.2. Clean Code.