Chuyển đến nội dung chính
Mô-đun 6 · Bài học 4 trong tổng số 529/50 trong khóa học~10 min
Bài học theo mô-đun (4/5)

Giao diện Stringer

fmt.Stringer có lẽ là giao diện tiêu chuẩn được sử dụng nhiều nhất trong tất cả các Thư viện tiêu chuẩn của Go. Đó là sự thể hiện hoàn hảo về sức mạnh của các giao diện nhỏ.

Sự định nghĩa

Go
type Stringer interface {
    String() string
}

Một chữ ký duy nhất. Bất kỳ loại nào xác định String() string đều thỏa mãn nó. Và fmt nhận ra giao diện này: khi bạn chuyển một giá trị tới fmt.Print*, nếu nó triển khai Stringer, kết quả của String() được sử dụng thay cho biểu diễn mặc định.

Ví dụ

Go
type Point struct{ X, Y int }

func (p Point) String() string {
    return fmt.Sprintf("(%d,%d)", p.X, p.Y)
}

fmt.Println(Point{1, 2})              // (1,2)
fmt.Printf("%v %s\n", Point{3, 4}, Point{5, 6})
// (3,4) (5,6)

Giá trị tương tự được in với định dạng tùy chỉnh trong %v, %s, Println, Sprint, log, v.v. Tất cả chỉ bằng cách xác định một phương thức.

Khi nó có ý nghĩa

  • Các loại mô hình miền (id, mã, tọa độ, liệt kê).
  • Lỗi về cách trình bày mà con người có thể đọc được (xem bài học tiếp theo về error).
  • Loại bạn đăng nhập thường xuyên và bạn muốn có định dạng cố định.

Không dành cho tất cả mọi thứ: nếu loại đơn giản và biểu diễn mặc định được thôi, đừng thêm tiếng ồn.

Các loại Enum: mẫu chung với iota

Go
type Status int

const (
    OK Status = iota
    WARN
    ERR
)

func (s Status) String() string {
    switch s {
    case OK:   return "OK"
    case WARN: return "WARN"
    case ERR:  return "ERR"
    default:   return "UNKNOWN"
    }
}

fmt.Println(OK, WARN, ERR)   // OK WARN ERR

Nếu không có String(), fmt.Println(OK) sẽ in 0. Với String(), nó in tên tượng trưng. stringer công cụ này tự động tạo ra mẫu này.

Coi chừng đệ quy vô hạn

Go
type T int
func (t T) String() string {
    return fmt.Sprintf("%d", int(t))  // OK: cast to int breaks the recursion
}

Stringer trên một máy thu con trỏ

Nếu String() có bộ thu con trỏ thì chỉ *T thỏa mãn Stringer. Với fmt.Println(t) (một giá trị), Go xem xét tập phương thức của T, không tìm thấy nó và sử dụng định dạng mặc định. Để tránh điều này, giá trị bộ thu thường được sử dụng cho String() (đó là "chế độ xem"; không phải sửa đổi bất cứ điều gì).

Hãy thử nó

tập thể dục#go.m6.l4.e1
Nỗ lực: 0Đang tải…

Triển khai String() trên Point để nó trả về (X,Y).

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Sử dụng `fmt.Sprintf` để định dạng các trường.

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m6.l4.e2
Nỗ lực: 0Đang tải…

Triển khai String() trên một loại Trạng thái (int): 0 → 'OK', 1 → 'WARN', nếu không thì → 'ERR'.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

Bật giá trị của máy thu; hãy nhớ cung cấp một mặc định.

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m6.l4.e3
Sẵn sàng

Phương thức này phải có chữ ký nào để thỏa mãn fmt.Stringer?

Go
func (x T) ??? {}
Tùy chọn trả lời

Tóm tắt lại

  • fmt.Stringer = interface { String() string }.
  • Việc triển khai nó sẽ thay đổi cách fmt.Print* thể hiện loại.
  • Thành ngữ cho enum (iota), loại tên miền, mã/số nhận dạng.
  • Không bao giờ gọi fmt.Sprintf("%s", t) trên t bên trong String() (đệ quy).
  • Quy ước: bộ nhận giá trị cho String() (không có đột biến, tránh "bất ngờ về phương thức đặt").