Giao diện
🦆 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.
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 == nil là false?
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:
- Extracted
OrderRepositoryinterface - Service depends on interface, not concrete db
- Business logic (calculating total) stays in service
- Database logic isolated in repository
- Mock repository for testing
📊 Summary
| Concept | Key Point |
|---|---|
| Implicit Implementation | No "implements" keyword, just match method signatures |
| iface Struct | Interface = (Type, Value) tuple |
| Nil Trap | Interface containing nil pointer ≠ nil interface |
| Empty Interface | Use sparingly, prefer generics or specific interfaces |
| DI Pattern | Accept 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.