-
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
241: Practice Exercise: Building a Plugin-Style Interface Registry
You've likely run into a situation where your switch statements are getting out of control. Maybe you're handling different file formats, different payment gateways, or different database drivers. Every time you add a new one, you have to go back to that one central block of code and add another case. It's tedious, and it violates the Open/Closed Principle.
The way we solve this in Go is by building a registry. Instead of the main logic knowing about every possible implementation, we create a central "phone book" where implementations can register themselves. I'm going to show you how to build this using a payment provider example.
Defining the Common Interface
First, we need a contract. Every payment provider we add must behave the same way, or the registry is useless. I'll start by defining a simple PaymentProcessor interface.
type PaymentProcessor interface {
Process(amount float64) error
}
Now, I can create a few concrete implementations. Let's do a Stripe one and a PayPal one. In a real project, these would probably live in their own packages.
type StripeProcessor struct{}
func (s *StripeProcessor) Process(amount float64) error {
fmt.Printf("Processing $%.2f via Stripe\n", amount)
return nil
}
type PayPalProcessor struct{}
func (p *PayPalProcessor) Process(amount float64) error {
fmt.Printf("Processing $%.2f via PayPal\n", amount)
return nil
}
Creating the Global Registry
Now for the core logic. I need a place to store these. A map is the obvious choice here, where the key is a string (the provider name) and the value is the interface.
var registry = make(map[string]PaymentProcessor)
func Register(name string, processor PaymentProcessor) {
registry[name] = processor
}
func GetProcessor(name string) (PaymentProcessor, error) {
p, ok := registry[name]
if !ok {
return nil, fmt.Errorf("processor %s not found", name)
}
return p, nil
}
Wait, I forgot about the state
Here is where I usually trip up the first time I build these. In the code above, I'm registering a singleton instance of the processor. For a simple stateless processor, that's fine. But what if my StripeProcessor needs a unique API key per request, or needs to maintain some internal state that shouldn't be shared across the whole app?
If I register a single instance, every part of my app is sharing that one object. That's a recipe for race conditions and weird bugs. I should be registering a factory function instead of the instance itself.
Let me pivot. I'll change the map to store functions that return a processor.
// The new registry type: a map of factory functions
var registry = make(map[string]func() PaymentProcessor)
func Register(name string, factory func() PaymentProcessor) {
registry[name] = factory
}
func GetProcessor(name string) (PaymentProcessor, error) {
factory, ok := registry[name]
if !ok {
return nil, fmt.Errorf("processor %s not found", name)
}
// We call the factory here to get a fresh instance
return factory(), nil
}
Wiring it up with init() functions
To make this feel like a real "plugin" system, I don't want my main.go to have to manually call Register() for every single provider. That defeats the purpose of decoupling.
Instead, I'll use Go's init() function. Since init() runs automatically when a package is initialized, the provider can "announce" itself to the registry just by being imported.
// In stripe_provider.go
func init() {
Register("stripe", func() PaymentProcessor {
return &StripeProcessor{}
})
}
// In main.go
import _ "myapp/providers/stripe" // The underscore import triggers init()
func main() {
proc, err := GetProcessor("stripe")
if err != nil {
log.Fatal(err)
}
proc.Process(100.00)
}
Now, if I want to add a "Square" provider, I just create a new package, implement the interface, and add an init() function. I never have to touch the GetProcessor logic or the main function's core loop ever again.
📋 Practical Task
Build a Multi-Format Document Exporter Registry
Your task is to implement a plugin-style registry for a document exporting system. Instead of payment processors, you will build a system that can export data to different formats (e.g., PDF, JSON, CSV).
- The Interface: Create a
DocumentExporterinterface with a methodExport(data string) error. - The Registry: Implement a registry that uses factory functions (not singletons) to store and retrieve exporters by a string key.
- The Implementations: Create at least two exporters (e.g.,
JSONExporterandCSVExporter) that print a message indicating the format being used. - The Automatic Setup: Use
init()functions to register your exporters so that they are available as soon as the package is imported. - The Test: In your
mainfunction, retrieve an exporter by name and call itsExportmethod. Handle the case where an invalid exporter name is requested.
There are no comments for now.