Skip to content

บทที่ 6 — Interfaces

← บทที่ 5 | สารบัญ | บทที่ 7 →

หลังจบบท คุณจะ:

  • เข้าใจว่า interface คือ "สัญญา" — ถ้า type ของเราทำงานตามที่สัญญาระบุ (มี method ครบ) ก็ใช้แทน interface ได้ทันที โดยไม่ต้องประกาศว่า "implement" เรียกสิ่งนี้ว่า implicit implementation (เข้าข่ายโดยปริยาย)
  • เขียน interface แบบ "เล็กและประกอบรวมกันได้" (small + composable)
  • ใช้ type assertion (ดึง type จริงออกมา) + type switch (แตกเคสตาม type)
  • รู้จัก empty interface (interface ว่าง รับค่าอะไรก็ได้) + any

ก่อนอ่านบทนี้ ควรเข้าใจ: บท 3 — Functions, บท 5 — Structs + Methods (โดยเฉพาะเรื่อง method + pointer receiver)

🗺️ บทนี้ยาว — อ่านยังไงไม่ให้ท้อ

Interface เป็นหัวใจของ Go แต่บทนี้ไต่ไปถึงระดับ advanced แบ่งเป็น 2 โซน:

โซนหัวข้อหมายเหตุ
แกนหลัก (ต้องเข้าใจ)ข้อ 1–8 — interface คืออะไร, implicit, small interface, stdlib (Reader/Stringer/error), empty interface/any, type assertion/switchจบโซนนี้ = ใช้ interface เขียนงานจริงได้
เจาะลึก (อ่านรอบสอง)ข้อ 9–21 — interface pollution, nil gotcha, embedding, design patterns, middleware, generics, reflect, iteratorกลับมาอ่านเมื่อเขียน Go คล่องแล้ว — มือใหม่ ข้ามไป Checkpoint ได้

👉 เพิ่งเริ่ม Go: อ่านถึง ข้อ 8 ให้เข้าใจจริง แล้วลองทำ Checkpoint — ของขั้นสูง (pattern, generics, reflect) ไว้ทีหลังไม่ผิด


1. Interface คืออะไร

Interface คือ "สัญญาเรื่องพฤติกรรม" — มันบอกว่า "type ใดก็ตามที่มี method เหล่านี้ ถือว่าใช้แทนกันได้" จุดพิเศษของ Go คือ implement แบบ implicit (โดยปริยาย) ไม่ต้องเขียน implements เหมือน Java — แค่ type มี method ครบตามที่ interface ต้องการ ก็ถือว่า "เข้าข่าย" อัตโนมัติ

"เข้าข่าย" แปลว่า type นั้นมี method ที่ interface ต้องการครบทุกตัว — เหมือนเช็คลิสต์ผ่านครบก็ถือว่าใช้แทนกันได้ ไม่ต้องประกาศความสัมพันธ์อะไรเพิ่ม:

text
Interface = "สัญญาเรื่องพฤติกรรม" (behavior contract)
   = "ถ้า type มี method นี้ → type นั้น implement interface นี้"

Go = "implicit" implementation (ไม่ต้อง implement keyword)
go
// ตัวอย่างนี้ต้อง import "fmt" ด้วย (ตัดออกเพื่อความกระชับ)

// Define interface
type Greeter interface {
    Greet() string
}

// Type ที่มี Greet() method = automatic implement
type English struct{}
func (e English) Greet() string { return "Hello!" }

type Thai struct{}
func (t Thai) Greet() string { return "สวัสดี!" }

// Use
func sayHi(g Greeter) {
    fmt.Println(g.Greet())
}

sayHi(English{})    // Hello!
sayHi(Thai{})       // สวัสดี!

ไม่มี class English implements Greeter แบบ Java
→ ถ้ามี method ตรง — auto-satisfy

(ต่างจาก C#: C# interface = nominal typing — ต้องประกาศ class English : IGreeter ตอนสร้าง class ให้ชัดเจน ส่วน Go interface = structural/implicit — มี method ครบก็ implement อัตโนมัติ ไม่ต้องประกาศความสัมพันธ์ใด ๆ เลย นี่คือ false-friend ที่สำคัญที่สุดของบทนี้สำหรับคนมาจาก .NET)


2. ทำไม Implicit ดีกว่า

text
Java/C# (explicit):
class English implements Greeter {
    public String greet() { ... }
}

→ Type ผูกกับ interface ตอนเขียน
→ ถ้าอยากให้ implement interface ใหม่ → ต้องแก้ class

Go (implicit):
type English struct{}
func (e English) Greet() string { ... }

→ Type ไม่รู้ด้วยซ้ำว่ามี interface "Greeter"
→ Author ของ Greeter ไม่ต้อง coordinate กับ author ของ English

ข้อดี:

  • Decoupling (การลดความผูกพัน) — แต่ละ package ไม่ต้องรู้จักกัน ทำให้แก้ไข/เปลี่ยน implementation ได้ง่าย
    • consumer (ฝั่งที่ใช้งาน) กำหนดสิ่งที่ตัวเองต้องการ
    • producer (ฝั่งที่สร้าง) ไม่ต้องรู้ด้วยซ้ำว่า interface นี้มีอยู่
  • ✅ Define interface "on consumer side" — ที่ใช้, ไม่ใช่ที่ producer
  • ✅ Test ง่าย — mock (ตัวปลอมสำหรับเทสต์) เป็นแค่ struct ที่มี method ตรงตามที่ interface กำหนด

3. Interface Idiom — Small + Single

ปรัชญาของ Go คือ interface ควร "เล็ก" (1-3 method) แล้วค่อยประกอบรวมกันเมื่อจำเป็น ต่างจาก Java ที่มักทำ interface ใหญ่รวมทุกอย่าง interface เล็กมีข้อดีสามอย่าง — ยืดหยุ่นกว่า ทดสอบง่ายกว่า และนำกลับไปใช้ในที่อื่นได้หลากหลายกว่า:

go
// ✅ Small interface (Go style)
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
}

// Compose
type ReadCloser interface {
    Reader
    Closer
}

type ReadWriter interface {
    Reader
    Writer
}

"The bigger the interface, the weaker the abstraction." — Rob Pike (ยิ่ง interface ใหญ่ ยิ่งพลังในการ abstract ลดลง — interface เล็กยืดหยุ่นและทรงพลังกว่า)แปลให้ชัดขึ้น: interface ที่มี method น้อยยิ่งยืดหยุ่นกว่า เพราะ type หลายชนิดทำตามได้ง่าย — interface ที่มี 20 method หาตัวที่ทำครบได้ยาก

→ Go favor small interface — 1-3 method

text
Java mindset:
interface UserService { 
    create(); read(); update(); delete(); search(); 
    bulkCreate(); bulkUpdate(); ... 
}

Go mindset:
type UserReader interface { Read(id) User }
type UserWriter interface { Write(u User) error }
type UserDeleter interface { Delete(id) error }

// Compose if need:
type UserStore interface {
    UserReader
    UserWriter
    UserDeleter
}

4. Standard Library Examples

วิธีเรียนรู้ interface ที่ดีที่สุดคือดูตัวจริงใน standard library ของ Go เอง — io.Reader, io.Writer, error, sort.Interface เป็นตัวอย่างของ interface เล็กที่ทรงพลัง มี type นับร้อยทั่ว ecosystem (ระบบนิเวศ = library/เครื่องมือรอบ ๆ ภาษา) ที่ implement มัน ทำให้เขียนโค้ดที่ใช้ร่วมกันได้:

io.Reader — anywhere with byte stream

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

// Many types implement:
- *os.File           // ไฟล์บนดิสก์
- *bytes.Buffer      // บัฟเฟอร์ในหน่วยความจำ
- *strings.Reader    // อ่าน string เป็น stream
- *http.Request.Body // body ของ HTTP request
- *gzip.Reader       // อ่านสตรีมที่บีบอัด gzip
- ... 100+ implementations

// Function accepts ANY:
func process(r io.Reader) error {
    buf := make([]byte, 1024)
    for {
        n, err := r.Read(buf)
        // io.EOF (End Of File = สิ้นสุดไฟล์/สตรีม — ไม่ใช่ error จริง
        // แต่เป็นสัญญาณว่าอ่านครบแล้ว)
        // เทียบตรง ๆ ด้วย err == io.EOF ก็พอสำหรับตัวอย่างนี้ (stdlib ไม่ wrap EOF)
        // (ในโค้ดจริงที่ซับซ้อนกว่านี้ มักใช้ errors.Is แทน — จะเรียนเต็ม ๆ ในบท 07)
        if err == io.EOF { break }
        if err != nil { return err }
        _ = n
        // ... process buf[:n]
    }
    return nil
}

// Use with anything
process(file)
process(strings.NewReader("hello"))
process(resp.Body)

fmt.Stringer

go
type Stringer interface {
    String() string
}

// fmt.Println(x) checks if x implements Stringer
type Color int

const (
    Red Color = iota
    Green
    Blue
)

func (c Color) String() string {
    switch c {
    case Red:   return "red"
    case Green: return "green"
    case Blue:  return "blue"
    default:    return fmt.Sprintf("Color(%d)", int(c))  // กัน panic ถ้าค่าเกินช่วง
    }
}

fmt.Println(Color(0))    // "red" not "0"

⚠️ ระวัง infinite recursion: ในบรรทัด default ต้อง cast เป็น int(c) ก่อนแล้วค่อย fmt.Sprintf("Color(%d)", int(c)) — ถ้าใช้ fmt.Sprintf("%v", c) แทน (ส่ง c ตรง ๆ) จะเกิด infinite recursion เพราะ %v เรียก String() ของ c ซ้ำ วนไม่จบ

error

go
type error interface {
    Error() string
}

type MyError struct {
    Code int
    Msg  string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("[%d] %s", e.Code, e.Msg)
}

func doSomething() error {
    return &MyError{Code: 404, Msg: "not found"}
}

5. Empty Interface — any (เดิมคือ interface{})

interface ที่ไม่มี method เลย ใน Go 1.18+ เขียนเป็น any (รูปเดิม interface{} ยังใช้ได้ — เป็นชื่อเทียบเท่าเชิงประวัติศาสตร์) ถือว่าทุก type "เข้าข่าย" — เก็บค่าอะไรก็ได้ (ถ้าเคยเขียนภาษาอื่นมาก่อน: คล้าย Object ของ Java หรือค่าใด ๆ ใน Python — ถ้าไม่รู้จักภาษาอื่นมาก่อน ข้ามส่วนเปรียบเทียบนี้ได้ แค่รู้ว่า any = เก็บอะไรก็ได้)

แต่ระวัง: การใช้ any ทำให้เสีย type safety (ตรวจชนิดตอน compile ไม่ได้) จึงควรใช้เท่าที่จำเป็น ถ้าทำได้ให้ใช้ generics หรือ type เฉพาะแทน:

go
// any (Go 1.18+) = alias ของ interface{} — รับค่าได้ทุกชนิด
var x1 any = 42
var x2 any = "hello"
var x3 any = []int{1, 2, 3}

// รูปเดิม (เทียบเท่ากันหมด):
var y1 interface{} = 42
var y2 interface{} = "hello"

fmt.Println signature

go
// Go 1.18+ — signature ปัจจุบัน
func Println(a ...any) (n int, err error)
// (ก่อน Go 1.18 เขียนเป็น ...interface{} — ทั้งสองเหมือนกันทุกประการ any = alias ของ interface{})

→ รับ argument อะไรก็ได้

Container

go
// Heterogeneous slice
mixed := []any{1, "two", 3.0, true}

for _, v := range mixed {
    fmt.Println(v)
}

⚠️ Don't Overuse

go
// ❌ Lose type safety
func process(data any) {
    // need type assertion to use
}

// ✅ Use generics (Go 1.18+) or specific type
func process[T any](data T)  {} // implementation omitted
func process2(data User)     {} // implementation omitted

6. Type Assertion

เมื่อมีค่าเก็บใน any แล้วอยากดึง type จริงออกมาใช้ ต้อง "type assertion" ด้วยรูป i.(string) — แต่ถ้า type ไม่ตรงจะ panic ดังนั้นควรใช้รูปปลอดภัย s, ok := i.(string) ที่คืน ok บอกว่าสำเร็จไหมแทนการ crash:

go
var i any = "hello"

// Assert i is string
s := i.(string)        // "hello"
fmt.Println(len(s))

// Panic if wrong type
n := i.(int)           // 💥 panic: interface conversion: ...
// ⚠️ ถ้ารันบรรทัดนี้จริง โปรแกรมจะ panic และหยุดทำงานทันที (บรรทัดถัดไปจะไม่ทำงานเลย)
// เขียนแบบนี้ต่อกันไว้แค่โชว์ตัวอย่าง ในโค้ดจริงต้องแยกไฟล์/ฟังก์ชันคนละที่

// Safe version with ok
s, ok := i.(string)    // "hello", true
n, ok := i.(int)       // 0, false (no panic)

if ok {
    fmt.Println(s)
}

7. Type Switch

go
func describe(i any) {
    switch v := i.(type) {
    case int:
        fmt.Printf("int: %d\n", v)
    case string:
        fmt.Printf("string: %s (len %d)\n", v, len(v))
    case bool:
        fmt.Printf("bool: %t\n", v)
    case []int:
        fmt.Printf("[]int: %v\n", v)
    case nil:
        fmt.Println("nil")
    default:
        fmt.Printf("unknown: %T = %v\n", v, v)
    }
}

describe(42)        // int: 42
describe("hi")      // string: hi (len 2)
describe(true)      // bool: true
describe(3.14)      // unknown: float64 = 3.14

ใช้ type switch เป็น "last resort" — ส่วนใหญ่ refactor ให้ใช้ interface ดีกว่า


8. Defining Behavior Through Interface

นี่คือพลังที่แท้จริงของ interface — ทำให้โค้ด "ไม่ผูกติด" กับ implementation ตัวใดตัวหนึ่ง ตัวอย่างเปรียบเทียบให้เห็นชัด: ถ้า service พึ่ง struct ตรง ๆ จะเปลี่ยน/เทสต์ยาก แต่ถ้าพึ่ง interface เราสลับ implementation (ไฟล์ → S3 → mock) ได้แค่เปลี่ยน argument:

Without Interface — Coupled

go
type FileStorage struct{}
func (f FileStorage) Save(data []byte) error { return nil } // implementation omitted

type UserService struct {
    storage FileStorage
}

// ต้อง import "encoding/json" ด้วย
func (s UserService) CreateUser(u User) error {
    data, _ := json.Marshal(u) // ในงานจริงต้องเช็ค err เสมอ
    return s.storage.Save(data)
}
text
ปัญหา:
- ต้อง use FileStorage เสมอ
- Test ลำบาก — file I/O จริง
- เปลี่ยน S3 → rewrite UserService

With Interface — Decoupled

go
// Define interface
type Storage interface {
    Save(data []byte) error
}

// Multiple implementations
type FileStorage struct{}
func (f FileStorage) Save(data []byte) error { return nil } // implementation omitted

type S3Storage struct{}
func (s S3Storage) Save(data []byte) error { return nil } // implementation omitted

type MockStorage struct {
    saved [][]byte
}
func (m *MockStorage) Save(data []byte) error {
    m.saved = append(m.saved, data)
    return nil
}

// Service depends on interface
type UserService struct {
    storage Storage
}

func NewUserService(s Storage) *UserService {
    return &UserService{storage: s}
}

// Usage
production := NewUserService(FileStorage{})
testing    := NewUserService(&MockStorage{})

→ Swap implementation = swap constructor argument


โซนเจาะลึก — ข้ามได้สำหรับมือใหม่

ตั้งแต่ข้อ 9 เป็นต้นไปเป็นเรื่อง design/pattern/internal ที่จะ "อิน" ก็ต่อเมื่อเขียน Go มาสักพัก — ถ้าเพิ่งเริ่ม ข้ามไป Checkpoint (ข้อ 21) ได้เลย แล้วกลับมาอ่านโซนนี้รอบสอง


9. Interface Pollution — Anti-pattern

เจาะลึก — ข้ามได้ถ้าเพิ่งเริ่ม

ระวังอีกด้านหนึ่ง — การสร้าง interface พร่ำเพรื่อก็เป็นปัญหา ถ้ามี implementation เดียวก็ไม่จำเป็นต้องมี interface เพราะเป็นแค่ boilerplate (โค้ดสำเร็จรูปที่ต้องเขียนซ้ำ ๆ ไม่เพิ่มคุณค่า)

Go มีคติว่า "Accept interfaces, return structs" — รับ interface เป็น parameter แต่คืน struct จริงเป็นผลลัพธ์:

go
// ❌ Don't define interface for every struct
type UserService interface {
    CreateUser(User) error
    GetUser(int) (User, error)
}

type UserServiceImpl struct{} // implementation omitted
text
ปัญหา:
- มี implementation เดียว
- Interface = "boilerplate" (โค้ดซ้ำที่ไม่เพิ่มคุณค่า) ที่ไม่มีประโยชน์
text
"Accept interfaces, return structs" — Go idiom

// Don't (คืน interface โดยไม่จำเป็น):
func NewUserService(...) UserServiceI { return &userServiceImpl{...} }

// Do (คืน struct จริง):
func NewUserService(...) *UserService { return &UserService{...} }

หมายเหตุ: ตั้งชื่อต่างกัน — UserServiceI (interface) กับ UserService (struct) เพื่อไม่ให้ชนกันในบรรทัดเดียวกัน

(ต่างจาก C#: C# นิยม prefix I นำหน้า interface เช่น IUserService — ตรงข้ามกับที่นี่ที่ต่อ suffix I ท้ายชื่อ Go ไม่มีธรรมเนียมนี้ตายตัว และจริง ๆ ไม่นิยม prefix/suffix บอก type ด้วยซ้ำ เพราะปกติควรมี interface เดียวไม่ต้องแยกชื่อ — เจอปัญหาแยกชื่อแบบนี้เฉพาะตอน migrate จาก concrete type ไปเป็น interface ระหว่างทาง)

When to Use Interface

text
✅ Multiple implementations exist
✅ Mock for testing
✅ Decouple package
✅ Define behavior in consumer package

❌ Just to "be flexible"
❌ One implementation
❌ Anticipated but not actual need

10. Define Interface Close to Use

หลักการที่ต่อจากข้อก่อน: ควรนิยาม interface ไว้ที่ "ฝั่งผู้ใช้" (consumer) ไม่ใช่ฝั่งผู้สร้าง (producer) — แพ็กเกจที่ใช้งานเป็นคนกำหนดว่าตัวเองต้องการ method อะไร ทำให้ลดการผูกพันระหว่างแพ็กเกจ (แพ็กเกจ producer ไม่ต้องรู้ด้วยซ้ำว่า interface นี้มีอยู่):

text
Common mistake (Java mindset):
package user/
   user.go         // type User struct
   service.go      // type UserService interface + UserServiceImpl

package handler/
   handler.go      // depends on user.UserService

→ user package own its interface
Go idiom:
package user/
   user.go         // type User struct
   service.go      // type Service struct (concrete, not interface)

package handler/
   handler.go
      // Define interface ที่ handler ต้องใช้
      type UserGetter interface { Get(id) User }
      
      // depends on UserGetter (defined here, not in user package)

→ handler defines what it needs
→ user package doesn't even know "UserGetter" exists

→ "Define interface in consumer, not producer"


11. Nil Interface — Gotcha

หัวข้อนี้เป็นระดับ intermediate+ — เนื้อหาเกี่ยวกับกลไกภายในของ interface ถ้าเพิ่งเริ่ม กด Ctrl+F แล้วค้นหา "Checkpoint" เพื่อข้ามไปได้เลย

เจาะลึก — กับดักที่เจอตอนเขียนจริง ไม่ต้องจำตอนนี้

นี่คือกับดักที่ทำให้โปรแกรมเมอร์ Go มากประสบการณ์ยังพลาด

(สำหรับคนมาจาก .NET: ใน C# null คือค่าเดียวไม่มีการแบ่ง type — bug แบบนี้เกิดไม่ได้ใน C# ดังนั้นเป็นเรื่องใหม่ทั้งหมด ไม่ใช่แค่รายละเอียดปลีกย่อยที่ต่างกันนิดหน่อย)

ทำไม: ภายใน interface value เก็บเป็น "คู่ของ (type, value)" — ไม่ใช่แค่ค่าเปล่า

ปัญหา: ถ้าคืน pointer ที่เป็น nil แต่ "ติด type มาด้วย" (typed nil = nil ที่มี type) — การเช็ค err != nil จะได้ true ทั้งที่ข้างในเป็น nil

ทางแก้: return nil ตรง ๆ อย่าคืนตัวแปร pointer ที่ตอนนี้เป็น nil ดูตัวอย่าง:

go
type MyError struct {
    msg string
}
func (e *MyError) Error() string { return e.msg }

func getErr() error {
    var err *MyError = nil
    return err            // returns "typed nil"
}

err := getErr()
if err != nil {
    fmt.Println("error!")     // 😱 prints — but should not
}

ทำไม?

text
Interface value = (type, value) pair
- nil interface: type=nil, value=nil
- typed nil: type=*MyError, value=nil

→ interface != nil because type is set!

Fix

go
func getErr() error {
    var err *MyError = nil
    if err == nil {
        return nil          // ✅ return untyped nil
    }
    return err
}

// Or just
func getErr() error {
    if condition {
        return &MyError{msg: "..."}
    }
    return nil              // ✅ untyped nil
}

12. Interface Embedding

เราประกอบ interface เล็ก ๆ เข้าด้วยกันเป็น interface ใหญ่ได้โดยการ "ฝัง" (embed) — เช่น ReadWriteCloser = Reader + Writer + Closer นี่คือวิธีที่ standard library สร้าง interface อย่าง io.ReadWriter, net.Conn สอดคล้องกับปรัชญา "interface เล็กแล้วประกอบ":

go
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
}

// Embed multiple
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

// = same as
type ReadWriteCloser interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
    Close() error
}

Standard library examples:

  • io.ReadWriter = Reader + Writer
  • io.ReadCloser = Reader + Closer
  • io.ReadWriteCloser
  • net.Conn = Reader + Writer + lots of network methods

13. Common Patterns

เจาะลึก — design pattern ระดับ intermediate+

interface เป็นเครื่องมือหลักในการทำ design pattern คลาสสิกหลายตัวใน Go ส่วนนี้รวมตัวอย่างที่ใช้บ่อย — Strategy (สลับอัลกอริทึม), Observer (ระบบ event/subscriber) และ Plugin architecture (ต่อยอดความสามารถ) ทั้งหมดอาศัย interface เป็นจุดเชื่อม:

Strategy Pattern

go
type SortStrategy interface {
    Sort([]int) []int
}

type BubbleSort struct{}
func (b BubbleSort) Sort(s []int) []int { return s } // implementation omitted

type QuickSort struct{}
func (q QuickSort) Sort(s []int) []int { return s } // implementation omitted

type Sorter struct {
    strategy SortStrategy
}

func (s Sorter) Sort(data []int) []int {
    return s.strategy.Sort(data)
}

// Usage
sorter := Sorter{strategy: QuickSort{}}
sorter.Sort([]int{3, 1, 2})

Observer Pattern

go
type Event struct {
    Type string
    Data any   // Go 1.18+ idiom — เทียบเท่า interface{}
}

type Handler interface {
    Handle(Event)
}

type EventBus struct {
    handlers []Handler
}

func (b *EventBus) Subscribe(h Handler) {
    b.handlers = append(b.handlers, h)
}

func (b *EventBus) Publish(e Event) {
    for _, h := range b.handlers {
        h.Handle(e)
    }
}

// Multiple handlers
type EmailHandler struct{}
func (h EmailHandler) Handle(e Event) { fmt.Println("email:", e.Type) } // implementation omitted

type LogHandler struct{}
func (h LogHandler) Handle(e Event) { fmt.Println("log:", e.Type) } // implementation omitted

bus := &EventBus{}
bus.Subscribe(EmailHandler{})
bus.Subscribe(LogHandler{})
bus.Publish(Event{Type: "user_created", Data: ...})

Plugin Architecture

go
type Plugin interface {
    Name() string
    Run() error
}

type App struct {
    plugins []Plugin
}

func (a *App) Register(p Plugin) {
    a.plugins = append(a.plugins, p)
}

func (a *App) RunAll() {
    for _, p := range a.plugins {
        log.Printf("Running %s", p.Name())
        p.Run()
    }
}

14. ตัวอย่างเต็ม — HTTP Middleware

ตัวอย่างจริงที่รวมพลังของ interface + function type — ระบบ middleware ของ HTTP server เทคนิคเด็ดคือ HandlerFunc ทำให้ฟังก์ชันธรรมดากลายเป็น type ที่ implement interface Handler ได้ แล้วห่อ handler ซ้อนกันเป็นชั้น ๆ (logging, auth) ได้อย่างยืดหยุ่น:

ตัวอย่างนี้ใช้ http.ResponseWriter/*http.Request จาก net/http ของ stdlib — ยังไม่ต้องเข้าใจ HTTP รายละเอียด เดี๋ยวบท 11 อธิบายเต็ม ตอนนี้โฟกัสที่ "interface + function type" พอ ถ้ายังว่ายแล้วก็ข้ามไปข้อ 15 ก่อนได้

Pattern นี้คือคู่ขนานของ http.Handler + http.HandlerFunc ใน stdlib ตรงตัว — เห็นบ่อยใน middleware library

go
type Handler interface {
    ServeHTTP(http.ResponseWriter, *http.Request)
}

type HandlerFunc func(http.ResponseWriter, *http.Request)

func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r)
}

// Middleware = function ที่รับ + return Handler
type Middleware func(Handler) Handler

// Logging middleware
func LoggingMiddleware(next Handler) Handler {
    return HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// Auth middleware
func AuthMiddleware(next Handler) Handler {
    return HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        if token == "" {
            http.Error(w, "unauthorized", 401)
            return
        }
        next.ServeHTTP(w, r)
    })
}

// Chain
func Chain(middlewares ...Middleware) Middleware {
    return func(final Handler) Handler {
        for i := len(middlewares) - 1; i >= 0; i-- {
            final = middlewares[i](final)
        }
        return final
    }
}

// Use
finalHandler := Chain(
    LoggingMiddleware,
    AuthMiddleware,
)(myHandler)

HandlerFunc Trick — เข้าใจให้ลึก

นี่คือทริคที่มือใหม่ Go ดูแล้วงงสุด แต่เจอแทบทุก library — ดูทีละบรรทัด:

go
// 1) ประกาศ type ใหม่ที่ "ค่าจริง" คือ function signature
type HandlerFunc func(http.ResponseWriter, *http.Request)

// 2) ใส่ method ServeHTTP ให้ type นี้ — body แค่เรียกตัวเองในฐานะ function
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r)            // ⭐ f เป็นค่าของ type HandlerFunc ซึ่งจริง ๆ เป็น function เลยเรียก f(...) ได้
}

ทำไมสำคัญ:

  • ตอนนี้ HandlerFunc มี method ServeHTTP → มัน implement interface Handler ทันที (เพราะ Go = implicit)
  • แปลว่า function ธรรมดา หลังจาก cast (แปลงชนิด) เป็น HandlerFunc แล้วจะใช้แทน Handler ได้
go
// function เปล่า ๆ
fn := func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "hello") // implementation omitted
}

// cast → กลายเป็น Handler ใช้ในที่ที่ต้องการ interface ได้เลย
var h Handler = HandlerFunc(fn)
h.ServeHTTP(w, r)

→ ทริคนี้ทำให้เราเขียน middleware/handler เป็น "function" ที่อ่านง่าย แล้วยังเสียบเข้ากับ ecosystem (ระบบนิเวศ = library/เครื่องมือรอบ ๆ ภาษา) ที่ต้องการ interface Handler ได้ทั้งหมด

HandlerFunc = type ที่ implement Handler interface — common trick


15. Generics + Interface (Go 1.18+, cmp.Ordered ต้องใช้ Go 1.21+)

เจาะลึก — ควรเข้าใจ interface พื้นฐาน (ข้อ 1–8) ให้แน่นก่อน

ตั้งแต่ Go 1.18 มี generics (เจเนอริก = เขียนโค้ดใช้ได้กับหลายชนิดข้อมูลโดยไม่ต้องเขียนซ้ำ) ที่ทำงานคู่กับ interface ได้

เราใช้ interface เป็น "constraint" (ข้อจำกัด/เงื่อนไขของชนิดที่รับได้ — เช่น Ordered = type ที่เปรียบเทียบมากกว่า/น้อยกว่ากันได้) บอกว่า type ที่รับได้ต้องมีคุณสมบัติอะไร

ประโยชน์: เขียนฟังก์ชันที่ใช้ได้หลาย type โดยยังคง type safety — ไม่ต้องพึ่ง any + type assertion อีกต่อไป:

go
import "cmp"   // Go 1.21+ — มี cmp.Ordered มาให้ใน stdlib

// Before generics — interface only (วิธีเก่า)
func Max(a, b interface{}) interface{} {
    // need type assertion
    // ❌ ตัวอย่างนี้ panic ถ้าไม่ใช่ int — นี่คือข้อจำกัดของวิธีเก่า ไม่ใช่ idiomatic code
    if a.(int) > b.(int) { return a }
    return b
}

// With generics (Go 1.21+ — ใช้ cmp.Ordered จาก stdlib)
func Max[T cmp.Ordered](a, b T) T {
    if a > b { return a }
    return b
}
// ก่อน Go 1.21 ใช้ `constraints.Ordered` จาก golang.org/x/exp/constraints แทน

Max(1, 2)        // int
Max(1.5, 2.5)    // float64
Max("a", "b")    // string

// Interface as constraint
type Number interface {
    int | float64
}

func Sum[T Number](nums []T) T {
    var sum T
    for _, n := range nums { sum += n }
    return sum
}

Sum([]int{1, 2, 3})            // 6
Sum([]float64{1.0, 2.5, 3.5})  // 7.0

→ Generics = ตัวเชื่อมระหว่างความปลอดภัยของชนิดข้อมูล (static type) กับความยืดหยุ่น


16. Common Mistakes

รวมข้อผิดพลาดเรื่อง interface ที่พบบ่อย — คืน interface โดยไม่จำเป็น, ทำ interface กว้างเกินไป, พึ่ง concrete type แทน interface (ทำให้ mock ยาก) และลืมว่า method set ของ value กับ pointer ต่างกัน (ทำให้ type ไม่ implement interface อย่างที่คิด):

Returning Concrete Type as Interface

go
// ❌
func NewUser() User { return User{} }       // returns concrete (good for clients)

// Vs:
type UserCreator interface { Create() User }
func New() UserCreator { return nil }    // ❌ unnecessary indirection

Wide Interface

go
// ❌ 20 methods — illustrative pseudocode, ไม่ใช่ Go syntax จริง
// type Repository interface {
//     Save(User) error; Update(User) error; Delete(int) error; Find(int) (User, error)
//     FindBy(filter string) ([]User, error); Search(q string) ([]User, error); Count() int
//     // ... อีกหลาย method
// }

// ✅ Smaller, focused
type UserSaver interface { Save(User) error }
type UserFinder interface { FindByID(int) (User, error) }
type UserDeleter interface { Delete(int) error }

Embed Concrete Type in Interface

go
// ❌ Mix concrete + interface
type App struct {
    db     *sql.DB        // concrete — hard to mock
    cache  *redis.Client
    logger *log.Logger
}

// ✅ Depend on interface
type App struct {
    db     DBExecutor    // interface
    cache  Cache         // interface
    logger Logger        // interface
}

Forget Pointer Receiver

go
type Counter struct{ count int }

func (c *Counter) Inc() { c.count++ }    // ✅ pointer

type Incrementer interface { Inc() }

var i Incrementer = Counter{}        // ❌ Counter doesn't implement (only *Counter)
var i Incrementer = &Counter{}       // ✅

→ Method set ของ value vs pointer ต่างกัน


17. Reflect — Avoid If Possible

เจาะลึกมาก — แม้แต่ Go dev มืออาชีพก็ใช้น้อย ชื่อหัวข้อบอกแล้วว่า "เลี่ยงถ้าทำได้"

go
import "reflect"

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

u := User{Name: "Anna", Age: 25}
t := reflect.TypeOf(u)
v := reflect.ValueOf(u)

for i := 0; i < t.NumField(); i++ {
    field := t.Field(i)
    value := v.Field(i)
    tag := field.Tag.Get("json")
    fmt.Printf("%s (%s) = %v\n", field.Name, tag, value)
}
// Name (name) = Anna
// Age (age) = 25

⚠️ Reflection ช้า + verbose (เขียนยุ่งยาก โค้ดยาว) + lose compile-time safety (เสีย type safety ตอน compile — error จะเจอตอนรันแทน)
→ ใช้น้อยที่สุด — เฉพาะ marshalling library, ORM, generic helper ก่อน Go 1.18


18. Iterator Pattern (Go 1.23+)

Go 1.23 เพิ่มความสามารถให้ for range วนบนฟังก์ชัน iterator ที่เราเขียนเองได้ — ทำให้สร้าง sequence แบบ lazy (คล้าย generator) แล้ววนด้วยไวยากรณ์ for range ที่คุ้นเคย:

go
// Iterator function
func Range(start, end int) func(yield func(int) bool) {
    return func(yield func(int) bool) {
        for i := start; i < end; i++ {
            if !yield(i) {
                return
            }
        }
    }
}

// Use with range
for i := range Range(0, 5) {
    fmt.Println(i)
}
// 0 1 2 3 4

19. ⚠️ Common Pitfalls

สรุปกับดักเรื่อง interface ทั้งหมดในบทไว้ในตารางเดียว คู่กับแนวทางที่ถูก ใช้เป็นเช็คลิสต์ทบทวนก่อนออกแบบ interface จริง:

Wide interface (10+ methods)small interface (1-3 methods)
Interface for one implementationconcrete struct
Return interface from constructorreturn concrete struct
Define interface in producer packagedefine in consumer package
Compare typed nil to nilreturn untyped nil explicitly
Method set forget pointer vs valueconsistent receiver type
Use reflection for genericuse generics (Go 1.18+)
Empty interface for everythingany only when truly heterogeneous

20. Idiomatic Go Interface

สรุปลักษณะของ interface ที่ถือว่า "เป็น Go แท้" (idiomatic) มี 4 อย่างหลัก — เล็ก, นิยามฝั่งผู้ใช้, ตั้งชื่อลงท้าย "-er", ใช้เพื่อพฤติกรรมไม่ใช่ข้อมูล ยึดแนวทางเหล่านี้แล้วโค้ดจะเข้ากับสไตล์ของชุมชน Go:

text
✅ Small (1-3 methods)
✅ Defined in consumer package
✅ Named with "-er" suffix (Reader, Writer, Stringer, Sorter)
✅ Accept interface, return struct
✅ Use to enable testing (mock = struct with methods)
✅ Compose with embedding (ReadWriter = Reader + Writer)
✅ Used for behavior, not data

21. Checkpoint

🛠️ Checkpoint 6.1 — Shape Interface (อินเทอร์เฟซรูปทรง)

go
type Shape interface {
    Area() float64
    Perimeter() float64
}

Implement (ทำให้ type เข้าข่าย interface): Rectangle, Circle, Triangle

  • function TotalArea(shapes []Shape) float64 (พื้นที่รวม)
  • function LargestShape(shapes []Shape) Shape (รูปที่ใหญ่สุด)

🛠️ Checkpoint 6.2 — Storage Interface (อินเทอร์เฟซที่เก็บข้อมูล)

go
type Storage interface {
    Save(key string, data []byte) error
    Load(key string) ([]byte, error)
    Delete(key string) error
}

Implement:

  • MemoryStorage (เก็บใน map ในหน่วยความจำ)
  • FileStorage (เก็บลงดิสก์)
  • MockStorage (ตัวปลอมสำหรับเทสต์)

ใช้ใน UserService โดยฉีด (inject = ส่งเข้ามาจากภายนอก) storage interface

🛠️ Checkpoint 6.3 — Plugin System (ระบบปลั๊กอิน)
สร้าง:

go
type Plugin interface {
    Name() string
    Init() error
    Run(input string) (output string, err error)
}
  • Plugin A: แปลงเป็นตัวพิมพ์ใหญ่ (uppercase)
  • Plugin B: นับคำ (word count)
  • Plugin C: กลับข้อความ (reverse)
  • ทะเบียนปลั๊กอิน (registry) + เรียกต่อกันเป็นทอด (chain execution)

🛠️ Checkpoint 6.4 — HTTP Middleware
ทำตัวอย่างข้อ 14 — เพิ่ม:

  • RateLimitMiddleware (จำกัดอัตราการเรียก)
  • CORSMiddleware (อนุญาตเรียกข้ามโดเมน)
  • RecoveryMiddleware (กู้จาก panic)

🛠️ Checkpoint 6.5 — Type Switch
สร้าง function Stringify(v any) string ที่:

  • int → strconv.Itoa
  • float64 → fmt.Sprintf("%.2f")
  • bool → "true"/"false"
  • []byte → string
  • struct → reflect to fields
  • nil → "nil"

22. สรุปบท

ทบทวนภาพรวมของบทนี้ — ตั้งแต่ interface คืออะไร, การ implement แบบ implicit, ปรัชญา interface เล็ก, type assertion/switch, ไปจนถึงกับดัก typed nil และการใช้ generics แทน empty interface:

สรุปสั้น (ภาษาไทย): interface ของ Go = "ประกาศพฤติกรรมที่ต้องการ" — ใครทำ method ตามได้ก็ใช้แทนกันได้ทันที (ไม่ต้องประกาศว่า implements) เน้นทำ interface เล็ก ๆ แล้วประกอบรวมกัน นิยามไว้ฝั่งคนใช้ ไม่ใช่ฝั่งคนสร้าง

Interface = behavior contract (สัญญาเรื่องพฤติกรรม) — define what, not how
Implicit implementation — มี method ตรง = auto-satisfy
✅ Go favors small interface (1-3 methods) + composition
io.Reader, io.Writer, Stringer, error = canonical examples
any = interface{} (Go 1.18+) — empty interface
Type assertion v.(T) + safe v, ok := x.(T)
Type switch switch v := x.(type) — multi-type handling
"Accept interfaces, return structs" — Go idiom
Define interface at consumer — not producer
✅ ⚠️ Typed nil ≠ nil ใน interface — beware
Generics (Go 1.18+) แทน interface{} for type-safe generic
✅ Use interface for: multiple impl, mocking, plugin, decouple package


← บทที่ 5 | บทที่ 7 → Errors