Skip to content

🦆 Interfaces & Reflection

"If it walks like a duck and quacks like a duck, it's a duck."
Go interfaces là implicit — bạn không "implement" chúng, bạn chỉ cần có methods đúng signature.

🎯 Philosophy: Implicit Implementation

No "implements" Keyword

go
// Định nghĩa interface
type Speaker interface {
    Speak() string
}

// Dog KHÔNG khai báo "implements Speaker"
type Dog struct {
    Name string
}

// Chỉ cần có method đúng signature
func (d Dog) Speak() string {
    return "Woof!"
}

// Dog tự động satisfy Speaker interface!
func main() {
    var s Speaker = Dog{Name: "Rex"}  // ✅ Works!
    fmt.Println(s.Speak())
}

🎓 Professor Tom's Deep Dive: Duck Typing vs Structural Typing

Go dùng Structural Typing, không phải Duck Typing thuần túy.

Duck Typing (Python/JS): Kiểm tra tại runtime

python
# Python - crash tại runtime nếu không có method
def make_speak(animal):
    animal.speak()  # 💥 AttributeError nếu không có speak()

Structural Typing (Go): Kiểm tra tại compile time

go
// Go - compile error nếu không satisfy interface
var s Speaker = Cat{}  // ❌ Compile error: Cat doesn't have Speak()

Kết quả: Type safety của static typing + flexibility của duck typing.


🧬 Interface Internals: The iface Struct

Interface = (Type, Value) Tuple

🎓 Under the Hood: iface Structure

Khi bạn assign một value vào interface, Go tạo một iface struct:

go
// runtime/runtime2.go (simplified)
type iface struct {
    tab  *itab          // Type information + method table
    data unsafe.Pointer // Pointer to actual data
}

type itab struct {
    inter *interfacetype // Interface type
    _type *_type         // Concrete type
    fun   [1]uintptr     // Method addresses (variable size)
}

Memory Layout:

┌─────────────────────────────────────────────────────────────────────┐
│                    Interface Variable                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   var s Speaker = Dog{Name: "Rex"}                                 │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                    iface struct                              │  │
│   ├─────────────────────────────────────────────────────────────┤  │
│   │  tab *itab ─────────────► ┌──────────────────────────────┐  │  │
│   │                           │ inter: *Speaker              │  │  │
│   │                           │ _type: *Dog                  │  │  │
│   │                           │ fun[0]: Dog.Speak address    │  │  │
│   │                           └──────────────────────────────┘  │  │
│   ├─────────────────────────────────────────────────────────────┤  │
│   │  data *Dog ─────────────► ┌──────────────────────────────┐  │  │
│   │                           │ Name: "Rex"                  │  │  │
│   │                           └──────────────────────────────┘  │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Interface Method Dispatch

go
// Khi gọi s.Speak():
// 1. Đọc iface.tab.fun[0] để lấy method address
// 2. Gọi method với iface.data làm receiver
// → Một level of indirection (nhưng rất nhanh)

⚠️ The Nil Interface Trap

🔥 Raizo's Critical Warning: Nil Pointer in Interface

Đây là senior interview question kinh điển!

go
type Speaker interface {
    Speak() string
}

type Dog struct {
    Name string
}

func (d *Dog) Speak() string {
    if d == nil {
        return "..."
    }
    return "Woof!"
}

func main() {
    var d *Dog = nil           // nil pointer
    var s Speaker = d          // Interface chứa nil pointer
    
    fmt.Println(s == nil)      // false! 🤯
    fmt.Println(s.Speak())     // "..." (không panic!)
}

Tại sao s == nilfalse?

Visual Explanation

┌─────────────────────────────────────────────────────────────────────┐
│              Nil Interface vs Interface with Nil Value             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   var s Speaker              // TRUE nil interface                  │
│   ┌─────────────────┐                                               │
│   │ tab:  nil       │        s == nil → TRUE                        │
│   │ data: nil       │                                               │
│   └─────────────────┘                                               │
│                                                                     │
│   ─────────────────────────────────────────────────────────────     │
│                                                                     │
│   var d *Dog = nil                                                  │
│   var s Speaker = d          // Interface with nil pointer!        │
│   ┌─────────────────┐                                               │
│   │ tab:  *Dog ───┐ │        s == nil → FALSE!                     │
│   │ data: nil     │ │        (tab != nil)                          │
│   └───────────────┼─┘                                               │
│                   ▼                                                 │
│           Type info exists                                          │
│           (just the VALUE is nil)                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Safe Pattern: Return Interface, Not Concrete Type

go
// ❌ DANGEROUS: Returns concrete type that becomes nil in interface
func GetSpeaker(useDog bool) Speaker {
    var d *Dog
    if useDog {
        d = &Dog{Name: "Rex"}
    }
    return d  // Nếu useDog=false, trả về interface{*Dog, nil} ≠ nil!
}

// ✅ SAFE: Return nil interface explicitly
func GetSpeakerSafe(useDog bool) Speaker {
    if useDog {
        return &Dog{Name: "Rex"}
    }
    return nil  // Returns true nil interface
}

func main() {
    s := GetSpeaker(false)
    if s != nil {
        s.Speak()  // Được gọi mặc dù d là nil pointer!
    }
    
    s2 := GetSpeakerSafe(false)
    if s2 != nil {
        s2.Speak()  // Không chạy vào đây ✅
    }
}

📦 Empty Interface: interface{} / any

The Universal Container

go
// interface{} có thể chứa BẤT KỲ type nào
var anything interface{}
anything = 42
anything = "hello"
anything = []int{1, 2, 3}
anything = struct{ Name string }{"Raizo"}

// Go 1.18+: any là alias cho interface{}
var x any = "hello"

Type Assertions

go
func process(v interface{}) {
    // Type assertion
    if s, ok := v.(string); ok {
        fmt.Println("String:", s)
        return
    }
    
    // Type switch (preferred)
    switch val := v.(type) {
    case int:
        fmt.Println("Int:", val)
    case string:
        fmt.Println("String:", val)
    case []int:
        fmt.Println("Slice of ints:", val)
    default:
        fmt.Printf("Unknown type: %T\n", val)
    }
}

🔥 Raizo's Pitfall: Overusing interface{}

interface{} says nothing — tránh lạm dụng!

go
// ❌ BAD: API không rõ ràng, mất type safety
func ProcessData(data interface{}) interface{} {
    // Caller không biết cần truyền gì, nhận gì
}

// ✅ GOOD: Dùng generics (Go 1.18+)
func ProcessData[T any](data T) T {
    // Type safety maintained
}

// ✅ GOOD: Dùng specific interface
type Processor interface {
    Process() Result
}

func ProcessData(p Processor) Result {
    return p.Process()
}

Chỉ dùng interface{} khi:

  • JSON/YAML parsing (unknown structure)
  • Printf-style variadic functions
  • Container libraries (pre-generics)

🏗️ Architecture: Dependency Injection

The Power of Interfaces

📌 HPN Standard: Accept Interfaces, Return Structs

go
// ✅ Correct: Accept interface
func NewUserService(repo UserRepository) *UserService { ... }

// ❌ Wrong: Accept concrete type
func NewUserService(repo *MySQLUserRepo) *UserService { ... }

Lý do:

  • Testability: Có thể inject mock
  • Flexibility: Swap implementations dễ dàng
  • Decoupling: Business logic không biết về storage details

Example: Storage Interface

go
// Define interface in the CONSUMER package (not provider!)
type Storage interface {
    Get(ctx context.Context, key string) ([]byte, error)
    Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
    Delete(ctx context.Context, key string) error
}

// MySQL implementation
type MySQLStorage struct {
    db *sql.DB
}

func (s *MySQLStorage) Get(ctx context.Context, key string) ([]byte, error) {
    var value []byte
    err := s.db.QueryRowContext(ctx, 
        "SELECT value FROM cache WHERE key = ?", key).Scan(&value)
    return value, err
}

func (s *MySQLStorage) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
    _, err := s.db.ExecContext(ctx,
        "INSERT INTO cache (key, value, expires_at) VALUES (?, ?, ?)",
        key, value, time.Now().Add(ttl))
    return err
}

func (s *MySQLStorage) Delete(ctx context.Context, key string) error {
    _, err := s.db.ExecContext(ctx, "DELETE FROM cache WHERE key = ?", key)
    return err
}

// Redis implementation
type RedisStorage struct {
    client *redis.Client
}

func (s *RedisStorage) Get(ctx context.Context, key string) ([]byte, error) {
    return s.client.Get(ctx, key).Bytes()
}

func (s *RedisStorage) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
    return s.client.Set(ctx, key, value, ttl).Err()
}

func (s *RedisStorage) Delete(ctx context.Context, key string) error {
    return s.client.Del(ctx, key).Err()
}

Using DI in Service

go
type CacheService struct {
    storage Storage  // Interface, not concrete type
}

func NewCacheService(storage Storage) *CacheService {
    return &CacheService{storage: storage}
}

func (s *CacheService) GetUser(ctx context.Context, userID string) (*User, error) {
    data, err := s.storage.Get(ctx, "user:"+userID)
    if err != nil {
        return nil, err
    }
    
    var user User
    if err := json.Unmarshal(data, &user); err != nil {
        return nil, err
    }
    return &user, nil
}

// Usage - swap easily!
func main() {
    // Development: Use MySQL
    mysqlStorage := &MySQLStorage{db: db}
    service := NewCacheService(mysqlStorage)
    
    // Production: Use Redis
    redisStorage := &RedisStorage{client: redisClient}
    service := NewCacheService(redisStorage)
}

Testing with Mocks

go
// Mock for testing
type MockStorage struct {
    data map[string][]byte
}

func (m *MockStorage) Get(ctx context.Context, key string) ([]byte, error) {
    if v, ok := m.data[key]; ok {
        return v, nil
    }
    return nil, errors.New("not found")
}

func (m *MockStorage) Set(ctx context.Context, key string, value []byte, ttl time.Duration) error {
    m.data[key] = value
    return nil
}

func (m *MockStorage) Delete(ctx context.Context, key string) error {
    delete(m.data, key)
    return nil
}

// Test
func TestCacheService_GetUser(t *testing.T) {
    mock := &MockStorage{
        data: map[string][]byte{
            "user:123": []byte(`{"id":"123","name":"Raizo"}`),
        },
    }
    
    service := NewCacheService(mock)
    
    user, err := service.GetUser(context.Background(), "123")
    assert.NoError(t, err)
    assert.Equal(t, "Raizo", user.Name)
}

🧩 Refactor Challenge

🎮 Code Challenge: Make It Testable

Đoạn code sau tightly coupled với database. Refactor để có thể test được!

go
// ❌ BEFORE: Tightly coupled, untestable
type OrderService struct {
    db *sql.DB
}

func (s *OrderService) CreateOrder(userID int64, items []Item) error {
    // Directly uses db
    tx, _ := s.db.Begin()
    
    var total float64
    for _, item := range items {
        total += item.Price * float64(item.Quantity)
    }
    
    _, err := tx.Exec("INSERT INTO orders (user_id, total) VALUES (?, ?)", 
        userID, total)
    if err != nil {
        tx.Rollback()
        return err
    }
    
    return tx.Commit()
}
Refactored Solution
go
// ✅ AFTER: Decoupled, testable

// 1. Define interface for what we need
type OrderRepository interface {
    CreateOrder(ctx context.Context, order *Order) error
}

// 2. Service depends on interface
type OrderService struct {
    repo OrderRepository  // Interface, not *sql.DB
}

func NewOrderService(repo OrderRepository) *OrderService {
    return &OrderService{repo: repo}
}

func (s *OrderService) CreateOrder(ctx context.Context, userID int64, items []Item) error {
    var total float64
    for _, item := range items {
        total += item.Price * float64(item.Quantity)
    }
    
    order := &Order{
        UserID: userID,
        Total:  total,
        Items:  items,
    }
    
    return s.repo.CreateOrder(ctx, order)
}

// 3. Production implementation
type SQLOrderRepository struct {
    db *sql.DB
}

func (r *SQLOrderRepository) CreateOrder(ctx context.Context, order *Order) error {
    tx, err := r.db.BeginTx(ctx, nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    _, err = tx.ExecContext(ctx,
        "INSERT INTO orders (user_id, total) VALUES (?, ?)",
        order.UserID, order.Total)
    if err != nil {
        return err
    }
    
    return tx.Commit()
}

// 4. Mock for testing
type MockOrderRepository struct {
    Orders []*Order
    Err    error
}

func (m *MockOrderRepository) CreateOrder(ctx context.Context, order *Order) error {
    if m.Err != nil {
        return m.Err
    }
    m.Orders = append(m.Orders, order)
    return nil
}

// 5. Now testable!
func TestOrderService_CreateOrder(t *testing.T) {
    mock := &MockOrderRepository{}
    service := NewOrderService(mock)
    
    items := []Item{{Price: 100, Quantity: 2}}
    err := service.CreateOrder(context.Background(), 1, items)
    
    assert.NoError(t, err)
    assert.Len(t, mock.Orders, 1)
    assert.Equal(t, float64(200), mock.Orders[0].Total)
}

Key Changes:

  1. Extracted OrderRepository interface
  2. Service depends on interface, not concrete db
  3. Business logic (calculating total) stays in service
  4. Database logic isolated in repository
  5. Mock repository for testing

📊 Summary

ConceptKey Point
Implicit ImplementationNo "implements" keyword, just match method signatures
iface StructInterface = (Type, Value) tuple
Nil TrapInterface containing nil pointer ≠ nil interface
Empty InterfaceUse sparingly, prefer generics or specific interfaces
DI PatternAccept interfaces, return structs

➡️ Tiếp theo

Interfaces nắm vững rồi! Tiếp theo: Slices & Maps Deep Dive - Slice internals, memory management, và performance.