Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
233: Adapter Pattern in Go
I see this all the time when I'm reviewing PRs: developers treat the Adapter pattern as if it's just a "wrapper" used for convenience or to hide a messy function. They'll wrap a three-line function in another function and call it an Adapter. But that's not what this pattern is for. If you're just trying to make a function call shorter or "cleaner," you're thinking of a helper function or perhaps a Facade.
Adapters aren't just "wrappers" for convenience
The biggest misconception is that the Adapter pattern is about simplifying code. It isn't. It's about compatibility. When you're using the Adapter pattern, you aren't trying to make a piece of code "easier" to use; you're trying to make a piece of code that has one interface work with a system that expects a completely different interface.
Imagine you have a system that processes payments using a PaymentProcessor interface. Your entire codebase depends on this interface. Now, your boss tells you to integrate a new, third-party payment vendor. You can't change the vendor's code (it's a compiled library), and you don't want to rewrite your entire application to accommodate their specific method signatures. If you just "wrap" their function for convenience, you're still stuck with a type mismatch. You need a bridge.
Bridging the gap between incompatible interfaces
In Go, the Adapter pattern relies heavily on our implicit interface implementation. The goal is to create a new type (the Adapter) that satisfies the interface your application expects, while internally delegating the actual work to the incompatible third-party object.
Let's look at a concrete example. Suppose your app expects this interface:
type PaymentProcessor interface {
Pay(amount float64) error
}
But the third-party library you just bought, LegacyBankAPI, looks like this:
type LegacyBankAPI struct{}
func (l *LegacyBankAPI) MakeTransaction(cents int, currency string) bool {
// Imagine complex legacy logic here
fmt.Printf("Processing %d cents in %s\n", cents, currency)
return true
}
You can't pass LegacyBankAPI into any function expecting a PaymentProcessor because MakeTransaction is not Pay. The signatures are completely different: one takes a float, the other takes an int and a string. One returns an error, the other returns a boolean.
Here is how I would handle this. I'd create an adapter that "translates" the request:
type BankAdapter struct {
api *LegacyBankAPI
}
func NewBankAdapter(api *LegacyBankAPI) *BankAdapter {
return &BankAdapter{api: api}
}
func (a *BankAdapter) Pay(amount float64) error {
// Translation logic: convert dollars to cents
cents := int(amount * 100)
// Call the incompatible method
success := a.api.MakeTransaction(cents, "USD")
if !success {
return fmt.Errorf("payment failed via legacy bank")
}
return nil
}
Now, the BankAdapter satisfies the PaymentProcessor interface. Your core business logic doesn't need to know that LegacyBankAPI even exists; it just calls Pay(), and the adapter handles the awkward translation. I've found that this is the only way to keep a codebase sane when you're forced to deal with rigid external dependencies.
- The Client: Your app, which expects the
PaymentProcessorinterface. - The Adaptee: The
LegacyBankAPI, which has the functionality but the wrong interface. - The Adapter:
BankAdapter, which maps the Client's needs to the Adaptee's capabilities.
📋 Practical Task
Implementing a Cloud Storage Adapter
You are building a file upload system. Your application uses a FileUploader interface to ensure that you can switch cloud providers easily. However, you've just integrated a new SDK called FastStorageSDK that doesn't follow your interface.
Your Task: Create an adapter that allows FastStorageSDK to be used wherever a FileUploader is required.
Here is the existing code:
package main
import (
"fmt"
)
// The interface your application expects
type FileUploader interface {
UploadFile(filename string, data []byte) error
}
// The third-party SDK (The Adaptee)
// You cannot modify this struct or its methods.
type FastStorageSDK struct{}
func (f *FastStorageSDK) PutObject(key string, content []byte, size int) bool {
fmt.Printf("FastStorage: Uploading %s (%d bytes)...\n", key, size)
return true
}
func main() {
sdk := &FastStorageSDK{}
// TODO: Create your adapter here and pass it into the ProcessUpload function
// var adapter FileUploader = ...
err := ProcessUpload(nil, "profile.jpg", []byte("fake-image-data"))
if err != nil {
fmt.Println("Error:", err)
}
}
func ProcessUpload(u FileUploader, name string, data []byte) error {
if u == nil {
return fmt.Errorf("uploader not provided")
}
return u.UploadFile(name, data)
}
Requirements:
- Define a
StorageAdapterstruct that wraps*FastStorageSDK. - Implement the
UploadFilemethod onStorageAdapter. - Inside
UploadFile, call the SDK'sPutObjectmethod, calculating the size of the data slice for the third argument. - Handle the boolean return value of
PutObject: if it returnsfalse, return an error; otherwise, returnnil. - Update
main()to instantiate your adapter and pass it toProcessUpload.
There are no comments for now.