Polymorphism via Embedded Interfaces
Swipe to show menu
When you embed an interface in a struct in Go, you enable that struct to satisfy the interface implicitly, while also allowing the struct to delegate behavior to any concrete implementation of the interface. This technique is a cornerstone of idiomatic Go design, especially for achieving polymorphism in backend systems. By embedding interfaces, you can swap out different implementations at runtime or during testing, leading to flexible and maintainable code. This pattern is especially useful for abstracting dependencies such as storage backends, logging, or external services, giving your application the ability to adapt to changing requirements without significant refactoring.
main.go
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556package main import ( "fmt" ) // Storage interface defines methods for a storage backend. type Storage interface { Save(key string, value string) error Load(key string) (string, error) } // FileStorage is a concrete implementation of Storage. type FileStorage struct { data map[string]string } func NewFileStorage() *FileStorage { return &FileStorage{data: make(map[string]string)} } func (fs *FileStorage) Save(key string, value string) error { fs.data[key] = value return nil } func (fs *FileStorage) Load(key string) (string, error) { val, ok := fs.data[key] if !ok { return "", fmt.Errorf("key not found") } return val, nil } // Service embeds the Storage interface for flexible backends. type Service struct { Storage } func main() { fileStorage := NewFileStorage() service := Service{Storage: fileStorage} err := service.Save("user42", "Alice") if err != nil { fmt.Println("Save error:", err) return } val, err := service.Load("user42") if err != nil { fmt.Println("Load error:", err) return } fmt.Println("Loaded value:", val) }
Embedding interfaces in structs is a powerful pattern for dependency injection and extensibility. By accepting any implementation of an interface, your struct becomes decoupled from specific dependencies, making it easy to swap out or mock those dependencies for testing. This approach also allows you to extend your application by introducing new implementations without modifying existing code, supporting the open/closed principle. In backend service design, embedding interfaces leads to more modular, testable, and maintainable systems.
Thanks for your feedback!
Ask AI
Ask AI
Ask anything or try one of the suggested questions to begin our chat