Why does Go favour small interfaces? Give examples of composition.
Question 96MediumGo 1.22 to 1.25
"The bigger the interface, the weaker the abstraction" (Rob Pike). A one-method interface is easy to satisfy, so many types implement it, and it composes well. io.Reader is implemented by files, sockets, gzip streams, HTTP bodies, strings.Reader and more. Every function that accepts an io.Reader works with all of them.
// Compose behaviour by wrapping small interfaces
func CountLines(r io.Reader) (int, error) {
sc := bufio.NewScanner(r)
n := 0
for sc.Scan() {
n++
}
return n, sc.Err()
}
f, _ := os.Open("log.gz")
defer f.Close()
gz, _ := gzip.NewReader(f) // io.Reader wrapping io.Reader
limited := io.LimitReader(gz, 10<<20) // cap at 10 MiB
tee := io.TeeReader(limited, sha256.New()) // hash while reading
n, err := CountLines(tee)
Common single-method interfaces: io.Reader, io.Writer, io.Closer, fmt.Stringer, error, http.Handler, sort.Interface (three methods), context.Context (four methods), and encoding.TextMarshaler.
By convention, a one-method interface is named after its method plus "-er" (Reader, Flusher). Larger contracts are built by embedding small interfaces (io.ReadWriteCloser) rather than by declaring one large interface.
More on Interfaces, Methods & Embedding
- Q94Go interfaces are satisfied implicitly. What design consequences follow, and where should interfaces be defined?
- Q95Explain "accept interfaces, return structs". When is returning an interface justified?
- Q97What's wrong with this
String()method? When doesfmtcallString()orError()? - Q98Design custom errors in Go. Explain wrapping,
errors.Is,errors.Asand multi-error trees. - Q99When does comparing two interface values panic? What does this print?
- Q100Does converting a value to an interface allocate? How do interfaces affect escape analysis and performance?