Composition Over Inheritance
Swipe to show menu
Go takes a unique approach to structuring code by favoring composition over inheritance. In many object-oriented languages such as Java or C++, you might use inheritance to create relationships between classes, allowing a subclass to inherit methods and fields from a parent. However, inheritance often leads to rigid class hierarchies that can be difficult to change or extend over time. Go deliberately avoids inheritance, encouraging you to build complex behaviors by composing small, focused types together. This makes your codebase more modular, easier to test, and more adaptable to evolving requirements.
main.go
123456789101112131415161718192021222324252627282930313233package main import ( "fmt" "time" ) // Logger provides logging capabilities. type Logger struct{} func (l Logger) Log(message string) { fmt.Printf("[%s] %s\n", time.Now().Format(time.RFC3339), message) } // Service composes Logger to gain logging functionality. type Service struct { Logger Name string } func (s Service) DoWork() { s.Log("Service " + s.Name + " is starting work.") // ... perform some work ... s.Log("Service " + s.Name + " has finished work.") } func main() { service := Service{ Logger: Logger{}, Name: "EmailSender", } service.DoWork() }
By embedding one struct within another, as you see with Service embedding Logger, you enable code reuse without tying your types into a rigid hierarchy. This approach lets you mix and match capabilities as needed, leading to more flexible backend architectures. You can add logging, metrics, or other cross-cutting concerns to any struct simply by embedding the relevant type, rather than forcing all your services to inherit from a single base class. This pattern is idiomatic in Go and helps you build systems that are both robust and easy to extend.
Thanks for your feedback!
Ask AI
Ask AI
Ask anything or try one of the suggested questions to begin our chat