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
EasyLet'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
AudioPlayerinterface that your modern system expects, with a methodPlay(track string) stringthat takes a track name and returns a playback message.Also define a legacy device that doesn't match this interface:
VinylPlayerstruct with aSpeedRPMfield (int) — it has a methodDropNeedle(record string, side string) stringthat returnsPlaying [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
VinylAdapterstruct that wraps aVinylPlayerand implements theAudioPlayerinterface. WhenPlayis called, the adapter should call the vinyl player'sDropNeedlemethod, defaulting toAfor the side parameter.Also create a modern player for comparison:
DigitalPlayerstruct — itsPlaymethod returnsStreaming: [track]
main.go: Demonstrate both players working through the same interface.Create a function
PlayMusic(player AudioPlayer, track string) stringthat 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 (
digitalorvinyl), 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 yourPlayMusicfunction, and print the result.
The following inputs will be provided:
- Line 1: Player type (
digitalorvinyl) - Line 2: RPM speed (only if vinyl)
- Last line: Track name to play
For example, given:
digital
Bohemian RhapsodyYour output should be:
Streaming: Bohemian RhapsodyAnd given:
vinyl
33
Abbey RoadYour output should be:
Playing Abbey Road (A side) at 33rpmAnd given:
vinyl
45
Blue MondayYour output should be:
Playing Blue Monday (A side) at 45rpmNotice 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
}
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 Parser11Standard Library & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json with Structshttp.Handler InterfaceRecap - REST API Models14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternMiddleware as Decorator3Pointers & 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