เรียน Go ภาษาง่าย 📘

🔌

เจาะลึก Interfaces + Methods + Embedding ฉบับภาษาง่าย

receiver, interface เล็ก, assertion, embedding

8 บท
12 ข้อสอบ
1. Method คือฟังก์ชันประจำตัว
go
type Person struct{ Name string }

// (p Person) = บอกว่านี่เป็นของ Person
func (p Person) Greet() string {
    return "สวัสดี " + p.Name
}
p := Person{Name: "Ann"}
fmt.Println(p.Greet()) // เรียกผ่านตัวเลย ไม่ต้อง Greet(p)

แปะได้กับ type ที่เราสร้างเองใน package เดียวกัน (ยกเว้น pointer interface โดยตรง)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. Pointer vs Value receiver เลือกยังไงไม่พัง
go
func (p Person) Read() string { return p.Name } // อ่านอย่างเดียว -> value ได้
func (p *Person) Grow() { p.Age++ }             // ต้องแก้ของจริง -> ต้อง *

กฎจำง่าย:

  • ต้องแก้ค่า / struct ใหญ่ / ต้องคง consistency ทั้ง type -> ใช้ * ทั้งหมด
  • แค่อ่าน + struct เล็ก -> value ได้
  • ห้ามผสมมั่ว: ถ้าตัวหนึ่งต้อง * ให้เป็น * ทั้ง type ไม่งั้น map/slice งง
go
// ดี: เป็น * ทั้งคู่
func (b *Buffer) Write(p []byte) (int, error)
func (b *Buffer) String() string
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. Interface เล็กดีกว่าใหญ่
go
type Stringer interface{ String() string } // มี 1 เมธอด = เป็นพวกเดียวกันอัตโนมัติ
func Print(s fmt.Stringer) { fmt.Println(s.String()) }
  • ใครมีเมธอดครบ = implement เอง ไม่ต้องประกาศ implements
  • เล็ก = ต่อกับใครก็ได้ (io.Reader io.Writer มีเมธอดเดียวแต่ใช้ทั้งโลก)
  • รับ interface เล็ก คืนของจริงก้อนใหญ่ อย่ารับ struct ใหญ่ถ้าใช้แค่ 1 เมธอด

ชื่อ interface เมธอดเดียวให้ลงท้าย er: Reader Writer Formatter Stringer

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. Type switch + assertion อย่างปลอดภัย
go
// แบบปลอดภัยมี ,ok
if s, ok := v.(fmt.Stringer); ok {
    fmt.Println(s.String())
}

// เช็คหลาย type
switch t := t.(type) {
default:
    fmt.Printf("ไม่รู้จัก %T\n", t)
case bool:
    fmt.Println("bool", t)
case int:
    fmt.Println("int", t)
case *int:
    fmt.Println("pointer int", *t)
}

ทริก t := t.(type) ในแต่ละ case t มี type ต่างกัน ใช้ชื่อเดิมได้เลย

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. Embedding + ของใหม่ struct key 1.27

ยัดกล่องในกล่อง เรียกของข้างในได้เลย (แทน inheritance):

go
type Logger struct{}
func (Logger) Log(s string) { fmt.Println("log:", s) }
type Job struct {
    Command string
    Logger // ไม่ต้องตั้งชื่อ field
}
j := Job{Command: "run"}
j.Log("start") // เหมือนเป็นของตัวเอง

ฝัง interface ก็ได้:

go
type ReaderWriter interface {
    io.Reader
    io.Writer
}

ของใหม่ 1.27: ตั้งค่า field ชั้นในได้เลยไม่ต้องห่อ:

go
type Habitat struct{ Burrow string }
type Gopher struct {
    Name string
    Habitat
}
g := Gopher{Name: "Gopher", Burrow: "Burrow #42"}

go fix embedlit ช่วยเปลี่ยนแบบเก่าเป็นแบบใหม่ให้

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


6. กับดักยอดฮิต
  • อย่าตั้งชื่อเมธอด Read Write String ถ้าความหมาย+signature ไม่เหมือน std (คนอ่านสับสน)
  • String() ห้ามเรียก Sprintf("%s", ตัวเอง) ตรงๆ จะวนไม่จบ ให้แปลงก่อน string(m)
  • อย่าคืน interface เปล่าๆ any ถ้ารู้ type ชัด (เสีย type safety)
  • nil interface vs nil pointer: interface ที่ห่อ pointer nil ไว้ != nil นะ เช็กดีๆ

อ้างอิง: Effective Go #methods #interfaces #embedding, Go 1.27 struct literal keys

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: รับ interface เล็กๆ ใช้ได้ทุกที่
go
func WriteAll(w io.Writer, lines []string) error {
    for _, l := range lines {
        if _, err := io.WriteString(w, l+"\n"); err != nil {
            return err
        }
    }
    return nil // รับ interface -> ใช้ได้ทั้งไฟล์ socket buffer
}
ตัวอย่างเพิ่ม 2: ฝัง interface เล็กเป็นใหญ่
go
type Reader interface{ Read([]byte) (int, error) }
type Writer interface{ Write([]byte) (int, error) }
type ReadWriter interface {
    Reader
    Writer // ฝัง 2 เล็กเป็น 1 ใหญ่
}
ตัวอย่างเพิ่ม 3: กับดัก nil ใน interface
go
var p *User // nil
var u fmt.Stringer = p
fmt.Println(u == nil) // false! interface ห่อ (type, nil) ไว้ ไม่ใช่ nil เปล่า
Best practices เช็กลิสต์
  • รับ interface เล็กๆ (io.Reader) คืน struct ชัดๆ (เรียกง่าย เทสต์ง่าย)
  • interface อยู่ฝั่งคนใช้ ไม่ใช่ฝั่งคนสร้าง (consumer defines)
  • อย่าตั้งชื่อเมธอดชน std (Read/Write/String) ถ้า signature ไม่เหมือน
  • String() ห้าม Sprintf("%s", ตัวเอง) ตรงๆ จะวนไม่จบ
  • ระวัง nil interface ที่ห่อ pointer nil (!= nil หลอกตา)
  • assertion ใช้ comma-ok v, ok := x.(T) อย่า assert ตรงๆ ถ้าไม่ชัวร์
  • อย่าคืน any ถ้ารู้ type ชัด (เสีย type safety เปล่าๆ)
แบบฝึกหัด
  • ออกแบบ Store interface ให้ todo app แล้วทำ 2 impl (memory + file)
  • เขียนเทสต์จับ nil-pointer-in-interface ให้ดูกับตา