Menu
Coddy logo textTech

Adapter Pattern

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 92 of 107.

The Adapter pattern allows incompatible interfaces to work together by wrapping an existing type with a new interface. While Command encapsulates actions, Adapter translates one interface into another that clients expect.

Imagine you have a legacy printer that uses a different method signature than your application expects:

// Target interface your application uses
type Printer interface {
    Print(text string) string
}

// Legacy printer with incompatible interface
type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

The adapter wraps the legacy type and implements the expected interface:

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

Now the legacy printer works seamlessly with code expecting the Printer interface:

func PrintMessage(p Printer, msg string) string {
    return p.Print(msg)
}

adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello")  // "Old printer: Hello (x1)"

The Adapter pattern is invaluable when integrating third-party libraries, working with legacy code, or when you need to use existing classes that don't match your interface requirements. It acts as a bridge without modifying the original code.

challenge icon

Challenge

Easy

Let's build a media player system using the Adapter pattern! You have a modern audio player that expects a unified interface, but you need to integrate legacy audio equipment that uses completely different method signatures. Your adapter will bridge this gap, allowing the old hardware to work seamlessly with the new system.

You'll organize your code across three files:

  • player.go: Define your target interface and a legacy audio device.

    Create an AudioPlayer interface that your modern system expects, with a method Play(track string) string that takes a track name and returns a playback message.

    Also define a legacy device that doesn't match this interface:

    • VinylPlayer struct with a SpeedRPM field (int) — it has a method DropNeedle(record string, side string) string that returns Playing [record] ([side] side) at [speed]rpm

    The VinylPlayer's interface is incompatible with AudioPlayer—it requires two parameters and uses a completely different method name.

  • adapter.go: Create your adapter that makes the legacy device compatible.

    Build a VinylAdapter struct that wraps a VinylPlayer and implements the AudioPlayer interface. When Play is called, the adapter should call the vinyl player's DropNeedle method, defaulting to A for the side parameter.

    Also create a modern player for comparison:

    • DigitalPlayer struct — its Play method returns Streaming: [track]
  • main.go: Demonstrate both players working through the same interface.

    Create a function PlayMusic(player AudioPlayer, track string) string that uses the interface to play music—this function shouldn't know or care whether it's using a digital player or an adapted vinyl player.

    Read a player type (digital or vinyl), and if vinyl, also read the RPM speed. Then read the track name. Create the appropriate player (using the adapter for vinyl), play the track through your PlayMusic function, and print the result.

The following inputs will be provided:

  • Line 1: Player type (digital or vinyl)
  • Line 2: RPM speed (only if vinyl)
  • Last line: Track name to play

For example, given:

digital
Bohemian Rhapsody

Your output should be:

Streaming: Bohemian Rhapsody

And given:

vinyl
33
Abbey Road

Your output should be:

Playing Abbey Road (A side) at 33rpm

And given:

vinyl
45
Blue Monday

Your output should be:

Playing Blue Monday (A side) at 45rpm

Notice how the PlayMusic function works identically with both players—it has no idea that the vinyl player is actually being adapted from a completely different interface. The adapter translates the modern interface calls into the legacy format behind the scenes!

Cheat sheet

The Adapter pattern allows incompatible interfaces to work together by wrapping an existing type with a new interface.

Define a target interface that your application expects:

type Printer interface {
    Print(text string) string
}

A legacy type with an incompatible interface:

type OldPrinter struct{}

func (o OldPrinter) PrintDocument(doc string, copies int) string {
    return fmt.Sprintf("Old printer: %s (x%d)", doc, copies)
}

Create an adapter that wraps the legacy type and implements the target interface:

type OldPrinterAdapter struct {
    oldPrinter OldPrinter
}

func (a OldPrinterAdapter) Print(text string) string {
    return a.oldPrinter.PrintDocument(text, 1)
}

Use the adapter to make the legacy type work with code expecting the target interface:

func PrintMessage(p Printer, msg string) string {
    return p.Print(msg)
}

adapter := OldPrinterAdapter{oldPrinter: OldPrinter{}}
result := PrintMessage(adapter, "Hello")  // "Old printer: Hello (x1)"

The Adapter pattern is useful for integrating third-party libraries, working with legacy code, or adapting existing classes to match required interfaces without modifying the original code.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// PlayMusic uses the AudioPlayer interface to play music
// This function works with any AudioPlayer implementation
// TODO: Implement this function to call player.Play(track) and return the result
func PlayMusic(player AudioPlayer, track string) string {
	// TODO: Implement this function
	return ""
}

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Read player type
	playerType, _ := reader.ReadString('\n')
	playerType = strings.TrimSpace(playerType)
	
	var player AudioPlayer
	var track string
	
	if playerType == "vinyl" {
		// Read RPM speed for vinyl
		rpmStr, _ := reader.ReadString('\n')
		rpmStr = strings.TrimSpace(rpmStr)
		rpm, _ := strconv.Atoi(rpmStr)
		
		// Read track name
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Create a VinylPlayer with the given RPM
		// TODO: Wrap it in a VinylAdapter
		// TODO: Assign to player variable
		_ = rpm // Remove this line when you use rpm
	} else {
		// Read track name
		track, _ = reader.ReadString('\n')
		track = strings.TrimSpace(track)
		
		// TODO: Create a DigitalPlayer
		// TODO: Assign to player variable
	}
	
	// TODO: Call PlayMusic and print the result
	_ = player // Remove this line when you use player
	_ = track  // Remove this line when you use track
}
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