Interface Composition
In this page:
type Name interface {
Interface1
Interface2
Method()
}
Embedding Interfaces
An interface can embed another interface simply by naming it inside its own body, which pulls in all of that interface's method requirements without repeating their signatures.
Example: Embedding Interfaces
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// Define an interface named Reader (a set of method signatures)
type Reader interface {
Read() string
}
// Define an interface named Writer (a set of method signatures)
type Writer interface {
Write(string)
}
type ReadWriter interface {
Reader
Writer
}
// Define a struct type named Buffer
type Buffer struct {
data string
}
// Define the method Read on type Buffer
func (b *Buffer) Read() string { return b.data }
// Define the method Write on type Buffer
func (b *Buffer) Write(s string) { b.data += s }
// main is where the program starts running
func main() {
var rw ReadWriter = &Buffer{}
rw.Write("hello")
fmt.Println(rw.Read())
}
Login to try C/C++/Java/PHP code in the editor
The Standard Library's io.ReadWriter
The standard library follows this exact pattern with io.ReadWriter, which embeds both io.Reader and io.Writer -- any type implementing both Read and Write methods automatically satisfies io.ReadWriter with no extra code.
Example: The Standard Library's io.ReadWriter
package main
import (
"bytes"
"fmt"
)
func main() {
var buf bytes.Buffer // *bytes.Buffer satisfies io.ReadWriter
buf.Write([]byte("composed interfaces"))
fmt.Println(buf.String())
}
Login to try C/C++/Java/PHP code in the editor
Satisfying a Composed Interface
To satisfy a composed interface, a type simply needs to implement every method required by each embedded interface -- there's nothing special about composition from the implementing type's point of view; it's just a larger method set to fulfill.
Example: Satisfying a Composed Interface
// Every Go file belongs to a package; main builds an executable
package main
// Import the fmt package
import "fmt"
// Define an interface named Named (a set of method signatures)
type Named interface {
Name() string
}
// Define an interface named Aged (a set of method signatures)
type Aged interface {
Age() int
}
type Person interface {
Named
Aged
}
// Define a struct type named Employee
type Employee struct {
name string
age int
}
// Define the method Name on type Employee
func (e Employee) Name() string { return e.name }
// Define the method Age on type Employee
func (e Employee) Age() int { return e.age }
// main is where the program starts running
func main() {
var p Person = Employee{name: "Nikhil", age: 29}
fmt.Println(p.Name(), p.Age())
}
Login to try C/C++/Java/PHP code in the editor
- Manually re-listing every method from another interface instead of simply embedding that interface by name.
- Assuming a composed interface changes how satisfaction works -- a type must still implement every method from all embedded interfaces, nothing is optional.
- Creating deeply nested composed interfaces that become hard to trace back to their required method set.
- Interfaces can embed other interfaces, combining their method sets into one larger interface.
- io.ReadWriter is a standard-library example: it embeds both io.Reader and io.Writer.
- A type must implement every method from all embedded interfaces to satisfy the composed interface.
- Composition lets you build larger contracts from small, well-understood pieces instead of duplicating method lists.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: