Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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 Player interface with a Play() string method
    • A Recorder interface with a Record(content string) string method
    • A MediaDevice interface that composes both Player and Recorder
    • A SmartDevice struct with a Name field that implements all the required methods
  • main.go: Create functions that demonstrate how the same SmartDevice can be used through different interface types. Write three functions:
    • UsePlayer that accepts a Player and calls its Play method
    • UseRecorder that accepts a Recorder and a content string, then calls Record
    • UseMediaDevice that accepts a MediaDevice and a content string, then calls both Record and Play
    Read the device name and content from input, create a 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 playing
  • Record(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 playing

The 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")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming