Embedding in Golang

Embedding in Golang

Embedding is a powerful feature in Go that allows you to include one type (struct or interface) within another type. This provides a way to compose types and achieve code reuse without inheritance. When you embed a type, the embedded type’s fields and methods become part of the embedding type, allowing for elegant composition patterns.

Struct Embedding

Struct embedding allows you to include one struct as a field in another struct without specifying a field name.

Basic Struct Embedding

basic-embedding.go
package main

import "fmt"

// Base struct
type Person struct {
    Name string
    Age  int
}

// Embedded in Employee
type Employee struct {
    Person      // Embedded struct
    EmployeeID  int
    Department  string
}

func main() {
    emp := Employee{
        Person: Person{
            Name: "John Doe",
            Age:  30,
        },
        EmployeeID: 12345,
        Department: "Engineering",
    }
    
    // Access embedded fields directly
    fmt.Println("Name:", emp.Name)
    fmt.Println("Age:", emp.Age)
    fmt.Println("Employee ID:", emp.EmployeeID)
    fmt.Println("Department:", emp.Department)
    
    // Access embedded struct explicitly
    fmt.Println("Person:", emp.Person)
}

Method Promotion

When you embed a struct, its methods are promoted to the embedding struct:

method-promotion.go
package main

import "fmt"

type Address struct {
    Street  string
    City    string
    Country string
}

func (a Address) FullAddress() string {
    return fmt.Sprintf("%s, %s, %s", a.Street, a.City, a.Country)
}

type Person struct {
    Name    string
    Age     int
    Address     // Embedded
}

func (p Person) Introduce() string {
    return fmt.Sprintf("Hi, I'm %s, %d years old", p.Name, p.Age)
}

func main() {
    person := Person{
        Name: "Alice",
        Age:  25,
        Address: Address{
            Street:  "123 Main St",
            City:    "Anytown",
            Country: "USA",
        },
    }
    
    // Call promoted method
    fmt.Println(person.FullAddress())
    
    // Call own method
    fmt.Println(person.Introduce())
}

Field Name Conflicts

When embedded structs have conflicting field names, you must access them explicitly:

field-conflicts.go
package main

import "fmt"

type Engine struct {
    Type   string
    Power  int
}

type Battery struct {
    Type   string
    Capacity int
}

type ElectricCar struct {
    Engine      // Embedded
    Battery     // Embedded
    Model       string
}

func main() {
    car := ElectricCar{
        Engine: Engine{
            Type:  "Electric Motor",
            Power: 150,
        },
        Battery: Battery{
            Type:     "Lithium-ion",
            Capacity: 75,
        },
        Model: "Tesla Model 3",
    }
    
    // Access conflicting fields explicitly
    fmt.Println("Engine Type:", car.Engine.Type)
    fmt.Println("Battery Type:", car.Battery.Type)
    fmt.Println("Engine Power:", car.Engine.Power)
    fmt.Println("Battery Capacity:", car.Battery.Capacity)
    fmt.Println("Model:", car.Model)
}

Interface Embedding

Interface embedding allows you to compose interfaces from other interfaces.

Basic Interface Embedding

interface-embedding.go
package main

import "fmt"

// Basic interfaces
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Closer interface {
    Close() error
}

// Embedded interface
type ReadWriter interface {
    Reader
    Writer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// Implementation
type File struct {
    name string
}

func (f *File) Read(p []byte) (n int, err error) {
    // Implementation...
    return len(p), nil
}

func (f *File) Write(p []byte) (n int, err error) {
    // Implementation...
    return len(p), nil
}

func (f *File) Close() error {
    // Implementation...
    return nil
}

func main() {
    var f ReadWriteCloser = &File{name: "test.txt"}
    
    // Can call all methods
    data := []byte("hello")
    f.Write(data)
    buffer := make([]byte, 100)
    f.Read(buffer)
    f.Close()
    
    fmt.Println("All operations completed")
}

Advanced Patterns

Composition over Inheritance

composition.go
package main

import "fmt"

// Behavior interfaces
type Flyer interface {
    Fly()
}

type Swimmer interface {
    Swim()
}

type Walker interface {
    Walk()
}

// Concrete implementations
type Bird struct {
    Name string
}

func (b Bird) Fly() {
    fmt.Printf("%s is flying\n", b.Name)
}

func (b Bird) Walk() {
    fmt.Printf("%s is walking\n", b.Name)
}

type Fish struct {
    Name string
}

func (f Fish) Swim() {
    fmt.Printf("%s is swimming\n", f.Name)
}

// Duck can do multiple things
type Duck struct {
    Bird  // Can fly and walk
    Fish  // Can swim (but we'll override)
}

func (d Duck) Swim() {
    fmt.Printf("%s is swimming like a duck\n", d.Bird.Name)
}

func main() {
    duck := Duck{
        Bird: Bird{Name: "Donald"},
        Fish: Fish{Name: "Donald"}, // Same name for simplicity
    }
    
    duck.Fly()  // From embedded Bird
    duck.Walk() // From embedded Bird
    duck.Swim() // Overridden in Duck
}

Embedded Mutex Pattern

embedded-mutex.go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    sync.Mutex // Embedded mutex
    count      int
}

func (sc *SafeCounter) Increment() {
    sc.Lock()         // Method promoted from sync.Mutex
    defer sc.Unlock()
    sc.count++
}

func (sc *SafeCounter) Value() int {
    sc.Lock()
    defer sc.Unlock()
    return sc.count
}

func main() {
    counter := SafeCounter{}
    
    for i := 0; i < 10; i++ {
        go counter.Increment()
    }
    
    // Give goroutines time to complete
    fmt.Println("Final count:", counter.Value())
}

Embedded Logger

embedded-logger.go
package main

import (
    "log"
    "os"
)

type Service struct {
    *log.Logger // Embedded logger
    name        string
}

func NewService(name string) *Service {
    return &Service{
        Logger: log.New(os.Stdout, name+": ", log.LstdFlags),
        name:   name,
    }
}

func (s *Service) Start() {
    s.Println("Service starting") // Method promoted from log.Logger
}

func (s *Service) Stop() {
    s.Println("Service stopping")
}

func main() {
    service := NewService("MyService")
    service.Start()
    service.Stop()
}

Method Overriding

When embedding types, you can override methods from the embedded type:

method-overriding.go
package main

import "fmt"

type Animal struct {
    Name string
}

func (a Animal) Speak() {
    fmt.Printf("%s makes a sound\n", a.Name)
}

type Dog struct {
    Animal // Embedded
    Breed  string
}

func (d Dog) Speak() {
    fmt.Printf("%s barks: Woof!\n", d.Animal.Name)
}

type Cat struct {
    Animal
    Color string
}

func (c Cat) Speak() {
    fmt.Printf("%s meows: Meow!\n", c.Animal.Name)
}

func main() {
    dog := Dog{
        Animal: Animal{Name: "Buddy"},
        Breed:  "Golden Retriever",
    }
    
    cat := Cat{
        Animal: Animal{Name: "Whiskers"},
        Color:  "Black",
    }
    
    dog.Speak() // Overridden method
    cat.Speak() // Overridden method
    
    // Access embedded method explicitly
    dog.Animal.Speak()
}

Initialization and Construction

Proper initialization is important when using embedding:

initialization.go
package main

import "fmt"

// Component that needs initialization
type DatabaseConnection struct {
    Host     string
    Port     int
    Connected bool
}

func (db *DatabaseConnection) Connect() {
    // Simulate connection
    db.Connected = true
    fmt.Printf("Connected to %s:%d\n", db.Host, db.Port)
}

func (db *DatabaseConnection) Disconnect() {
    db.Connected = false
    fmt.Println("Disconnected")
}

// Service that embeds DatabaseConnection
type UserService struct {
    *DatabaseConnection // Embedded pointer
    ServiceName         string
}

func NewUserService(host string, port int, serviceName string) *UserService {
    db := &DatabaseConnection{
        Host: host,
        Port: port,
    }
    
    service := &UserService{
        DatabaseConnection: db,
        ServiceName:        serviceName,
    }
    
    // Initialize the embedded component
    db.Connect()
    
    return service
}

func (us *UserService) GetUser(id int) string {
    if !us.Connected {
        return "Not connected"
    }
    return fmt.Sprintf("User %d from %s", id, us.ServiceName)
}

func main() {
    service := NewUserService("localhost", 5432, "UserDB")
    fmt.Println(service.GetUser(123))
    service.Disconnect()
}

Common Patterns

Decorator Pattern

decorator.go
package main

import "fmt"

type Component interface {
    Operation() string
}

type ConcreteComponent struct{}

func (c ConcreteComponent) Operation() string {
    return "ConcreteComponent"
}

type Decorator struct {
    Component // Embedded interface
}

func (d Decorator) Operation() string {
    return fmt.Sprintf("Decorator(%s)", d.Component.Operation())
}

type ConcreteDecoratorA struct {
    Decorator // Embedded struct
    addedState string
}

func (d ConcreteDecoratorA) Operation() string {
    return fmt.Sprintf("ConcreteDecoratorA(%s, %s)", d.Component.Operation(), d.addedState)
}

func main() {
    component := ConcreteComponent{}
    decoratorA := ConcreteDecoratorA{
        Decorator: Decorator{
            Component: component,
        },
        addedState: "extra feature",
    }
    
    fmt.Println(decoratorA.Operation())
}

Strategy Pattern

strategy.go
package main

import "fmt"

type SortStrategy interface {
    Sort([]int)
}

type BubbleSort struct{}

func (bs BubbleSort) Sort(data []int) {
    fmt.Println("Sorting with bubble sort")
    // Bubble sort implementation...
}

type QuickSort struct{}

func (qs QuickSort) Sort(data []int) {
    fmt.Println("Sorting with quick sort")
    // Quick sort implementation...
}

type Sorter struct {
    SortStrategy // Embedded interface
}

func (s Sorter) SortData(data []int) {
    s.SortStrategy.Sort(data)
}

func main() {
    data := []int{3, 1, 4, 1, 5}
    
    bubbleSorter := Sorter{SortStrategy: BubbleSort{}}
    bubbleSorter.SortData(data)
    
    quickSorter := Sorter{SortStrategy: QuickSort{}}
    quickSorter.SortData(data)
}

Best Practices

  1. Use embedding for composition - Prefer composition over inheritance
  2. Keep embedded types simple - Embedded types should have clear, focused responsibilities
  3. Document embedded fields - Make it clear what types are embedded
  4. Avoid deep embedding hierarchies - Keep embedding levels shallow
  5. Use pointer embedding carefully - Be aware of nil pointer issues
  6. Override methods intentionally - Only override methods when you need different behavior
  7. Initialize embedded types properly - Ensure embedded components are properly initialized

Potential Pitfalls

Nil Pointer with Embedded Pointers

pitfall-nil.go
package main

import "fmt"

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    if l == nil {
        fmt.Println("Logger is nil")
        return
    }
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

type Service struct {
    *Logger // Embedded pointer
}

func main() {
    // This will panic because Logger is nil
    // service := Service{}
    // service.Log("test")
    
    // Correct way
    service := Service{
        Logger: &Logger{prefix: "INFO"},
    }
    service.Log("Service started")
    
    // Or initialize in constructor
    service2 := &Service{}
    service2.Logger = &Logger{prefix: "DEBUG"}
    service2.Log("Service initialized")
}

Method Name Conflicts

pitfall-conflicts.go
package main

import "fmt"

type A struct {
    value int
}

func (a A) Get() int {
    return a.value
}

type B struct {
    value int
}

func (b B) Get() int {
    return b.value * 2
}

type C struct {
    A // Embedded
    B // Embedded
}

func main() {
    c := C{
        A: A{value: 10},
        B: B{value: 5},
    }
    
    // This would be ambiguous
    // fmt.Println(c.Get())
    
    // Must call explicitly
    fmt.Println("A.Get():", c.A.Get())
    fmt.Println("B.Get():", c.B.Get())
}

External Resources:

Related Tutorials:

Last updated on