Hiểu về Methods và Interface trong Go

Phương pháp lập trình hướng đối tượng

Phần trước đã giới thiệu hàm (function) giúp tái sử dụng code, đó là kiểu lập trình hàm. Lập trình hướng đối tượng (OOP) dựa trên đối tượng và phương thức, cho phép thao tác trên một đối tượng cụ thể thay vì chỉ gọi hàm đơn thuần.

So sánh hàm và phương thức


// Cách tiếp cận với hàm
func an(nguoi string) {}
func ngu(nguoi string) {}
func choi(nguoi string) {}

// Cách tiếp cận với đối tượng và phương thức
// xiaoHong.an(), xiaoHong.ngu(), xiaoHong.choi()
// Phương thức gắn liền với đối tượng thực hiện hành động

Phương thức đối tượng trong Go

Kiểu tùy chỉnh và phương thức

package main

import "fmt"

type MyInteger int

func (m MyInteger) Display() {
    fmt.Printf("Giá trị hiện tại: %d\n", m)
}

func main() {
    var val MyInteger = 5
    val.Display()
}

Khác biệt giữa hàm và phương thức:

  • Hàm: func tenHam(thamSo) (kqTraVe)
  • Phương thức: func (bienNhan kieuNhan) tenPhuongThuc(thamSo) (kqTraVe)

Bộ nhận (Receiver) trong phương thức

Bộ nhận là đối tượng mà phương thức tác động lên. Nó tạo sự liên kết giữa kiểu dữ liệu và phương thức.

package main

import "fmt"

type Person struct {
    name string
    age  uint8
}

func (p *Person) Eat() {
    fmt.Printf("%s đang ăn...\n", p.name)
}

func (p *Person) Sleep() {
    fmt.Printf("%s đang ngủ...\n", p.name)
}

func (p *Person) Play(game string) {
    fmt.Printf("%s đang chơi: %s...\n", p.name, game)
}

func main() {
    p := &Person{name: "Eson", age: 2}
    p.Eat()
    p.Sleep()
    p.Play("football")
}

Tổng kết về OOP trong Go

  • Mọi kiểu dữ liệu tùy chỉnh đều có thể định nghĩa phương thức (ngoại trừ kiểu built-in và interface)
  • Phương thức được liên kết với kiểu dữ liệu thông qua bộ nhận
  • Struct thường được dùng làm kiểu dữ liệu cho phương thức, và nên dùng con trỏ để dễ dàng thay đổi giá trị

Kế thừa thông qua Composition

Go không hỗ trợ từ khóa extends, thay vào đó dùng composition (hợp thành) để thay thế kế thừa.

package main

import "fmt"

type Animal struct {
    Name string
}

func (a *Animal) Eat() {
    fmt.Printf("%v đang ăn\n", a.Name)
}

type Cat struct {
    *Animal
}

func main() {
    cat := &Cat{
        Animal: &Animal{Name: "Mimi"},
    }
    cat.Eat() // Mimi đang ăn
}

Interface trong Go

Interface trong Go là một kiểu dữ liệu trừu tượng, tập hợp các phương thức. Nó chỉ quan tâm đến hành vi (phương thức), không quan tâm đến thuộc tính (dữ liệu).

Tại sao cần Interface?

Giả sử ta có hệ thống cảnh báo (alert) với SMS, WeChat... Nếu không dùng interface, code sẽ trở nên rất lộn xộn với nhiều logic lặp lại.

package main

import "fmt"

type Alerter interface {
    InsertAlarm()
    IsAlarm() bool
    SendAlarm()
}

type SmsAlarms struct {
    ModuleName  string
    PhoneNumber int
}

func (s *SmsAlarms) InsertAlarm() {
    fmt.Printf("SMS - Module: %s ghi cảnh báo vào DB\n", s.ModuleName)
}

func (s *SmsAlarms) IsAlarm() bool {
    fmt.Printf("SMS - Module: %s kiểm tra trạng thái cảnh báo\n", s.ModuleName)
    return true
}

func (s *SmsAlarms) SendAlarm() {
    fmt.Printf("SMS - Module: %s gửi cảnh báo thành công\n", s.ModuleName)
}

type WechatAlarms struct {
    ModuleName string
    Account    string
}

func (w *WechatAlarms) InsertAlarm() {
    fmt.Printf("WeChat - Module: %s ghi cảnh báo vào DB\n", w.ModuleName)
}

func (w *WechatAlarms) IsAlarm() bool {
    fmt.Printf("WeChat - Module: %s kiểm tra trạng thái cảnh báo\n", w.ModuleName)
    return true
}

func (w *WechatAlarms) SendAlarm() {
    fmt.Printf("WeChat - Module: %s gửi cảnh báo thành công\n", w.ModuleName)
}

func main() {
    var alertSystem Alerter
    
    var choice string
    fmt.Scanln(&choice)
    
    switch choice {
    case "sms":
        alertSystem = &SmsAlarms{ModuleName: "nginx", PhoneNumber: 1234567890}
    case "wechat":
        alertSystem = &WechatAlarms{ModuleName: "nginx", Account: "test@qq.com"}
    default:
        fmt.Println("Không gửi cảnh báo")
        return
    }
    
    alertSystem.InsertAlarm()
    if alertSystem.IsAlarm() {
        alertSystem.SendAlarm()
    }
}

Tác dụng của Interface

  • Interface tạo ra một hợp đồng (contract) mà các kiểu dữ liệu phải tuân theo
  • Giảm thiểu code trùng lặp, dễ mở rộng hệ thống
  • Khi thêm kiểu cảnh báo mới, chỉ cần implement các phương thức của interface

Lồng Interface

package main

import "fmt"

type Eater interface {
    Eat(food string)
}

type Walker interface {
    Walk(place string)
}

type Animaler interface {
    Eater
    Walker
}

type Cat struct {
    Name string
}

func (c *Cat) Eat(food string) {
    fmt.Printf("Mèo %s đang ăn %s\n", c.Name, food)
}

func (c *Cat) Walk(place string) {
    fmt.Printf("Mèo %s đang đi ở %s\n", c.Name, place)
}

func main() {
    var animal Animaler
    animal = &Cat{Name: "Mi"}
    animal.Eat("cá")
    animal.Walk("vườn")
}

Empty Interface (interface{})

Interface rỗng không có phương thức nào, do đó mọi kiểu dữ liệu đều implement nó. Nó cho phép lưu trữ giá trị của bất kỳ kiểu nào.

package main

import "fmt"

func main() {
    var data interface{}
    
    data = "Hello"
    fmt.Printf("Kiểu: %T, Giá trị: %v\n", data, data)
    
    data = 42
    fmt.Printf("Kiểu: %T, Giá trị: %v\n", data, data)
}

Ứng dụng của Empty Interface

  • Làm tham số hàm: cho phép hàm nhận mọi kiểu dữ liệu
  • Làm kiểu phần tử trong slice hoặc map: tăng tính linh hoạt
// Hàm nhận mọi kiểu
func printAny(x interface{}) {
    fmt.Printf("Kiểu: %T, Giá trị: %v\n", x, x)
}

// Slice với các phần tử kiểu khác nhau
items := []interface{}{10, "text", []int{1, 2, 3}}

// Map với giá trị kiểu khác nhau
info := map[string]interface{}{
    "age":   25,
    "city":  "Hanoi",
    "hobby": []string{"reading", "travel"},
}

Type Assertion (Xác nhận kiểu)

Để lấy lại giá trị thực từ empty interface, ta dùng cú pháp x.(T):

package main

import "fmt"

func main() {
    var value interface{}
    value = "Hello World"
    
    str, ok := value.(string)
    if ok {
        fmt.Printf("Chuỗi: %v\n", str)
    } else {
        fmt.Println("Không phải chuỗi")
    }
}

Thẻ: Go interface struct method receiver

Đăng vào ngày 6 tháng 7 lúc 03:21