Interface Composition
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 31 of 107.
Go allows you to build larger interfaces by composing smaller ones. Instead of defining one big interface with many methods, you embed existing interfaces inside a new interface definition.
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter combines both interfaces
type ReadWriter interface {
Reader
Writer
}The ReadWriter interface now requires both Read() and Write() methods. Any type that implements both methods satisfies ReadWriter, as well as Reader and Writer individually.
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File satisfies ReadWriter
}This composition approach keeps interfaces small and focused. You can mix and match them as needed, creating precise contracts for your functions. Go's standard library uses this pattern extensively, such as io.ReadWriter which combines io.Reader and io.Writer.
Challenge
EasyLet's build a media player system that demonstrates interface composition. You'll create small, focused interfaces and then combine them into a more powerful composed interface that a single type can satisfy.
You'll organize your code across two files:
media.go: Define three interfaces and a struct that implements them all:- A
Playerinterface with aPlay() stringmethod - A
Recorderinterface with aRecord(content string) stringmethod - A
MediaDeviceinterface that composes bothPlayerandRecorder - A
SmartDevicestruct with aNamefield that implements all the required methods
- A
main.go: Create functions that demonstrate how the sameSmartDevicecan be used through different interface types. Write three functions:UsePlayerthat accepts aPlayerand calls itsPlaymethodUseRecorderthat accepts aRecorderand a content string, then callsRecordUseMediaDevicethat accepts aMediaDeviceand a content string, then calls bothRecordandPlay
SmartDevice, and demonstrate using it through all three interface types.
The following inputs will be provided:
- Line 1: Device name
- Line 2: Content to record
Your methods should return strings in these formats:
Play():[Name] is playingRecord(content):[Name] recorded: [content]
For example, given MyPhone and voice memo, your output should be:
MyPhone is playing
MyPhone recorded: voice memo
MyPhone recorded: voice memo
MyPhone is playingThe first two lines show the device used through the individual interfaces (Player and Recorder). The last two lines show it used through the composed MediaDevice interface, which requires both capabilities. Notice how your single SmartDevice satisfies all three interfaces because it has both required methods.
Cheat sheet
Go allows you to build larger interfaces by composing smaller ones by embedding existing interfaces inside a new interface definition:
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter combines both interfaces
type ReadWriter interface {
Reader
Writer
}Any type that implements all methods from the embedded interfaces satisfies the composed interface:
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File satisfies ReadWriter
}A type that implements a composed interface also satisfies each of the individual embedded interfaces. This composition approach keeps interfaces small and focused while allowing you to create precise contracts for your functions.
Try it yourself
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Implement UsePlayer that accepts a Player and calls its Play method
// It should print the result of Play()
// TODO: Implement UseRecorder that accepts a Recorder and a content string
// It should print the result of Record(content)
// TODO: Implement UseMediaDevice that accepts a MediaDevice and a content string
// It should print the result of Record(content) and then Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
deviceName := scanner.Text()
scanner.Scan()
content := scanner.Text()
// TODO: Create a SmartDevice with the given name
// TODO: Use the device through the Player interface
// TODO: Use the device through the Recorder interface
// TODO: Use the device through the MediaDevice interface
_ = deviceName
_ = content
fmt.Println("Implement the solution")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool