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

Mẫu hàm tạo `Mới...`

Cờ vây không có từ khóa constructor. Hội nghị là một nhà máy hàm trả về giá trị hoặc con trỏ được khởi tạo đúng cách. Bởi tên thường được gọi là NewT (hoặc NewXxx).

Mẫu cơ sở: NewT

Go
type User struct {
    Name string
    Age  int
}

func NewUser(name string, age int) *User {
    return &User{Name: name, Age: age}
}

u := NewUser("Ada", 36)
fmt.Println(u.Name)

Lợi ích so với nghĩa đen trực tiếp &User{...}:

  • Bạn tập trung xác thực và mặc định.
  • Bạn có thể có các trường chưa được xuất và chỉ điền chúng từ hàm tạo.
  • Bạn có thể thay đổi cách thực hiện mà không làm gián đoạn người gọi.

Trình xây dựng có xác thực: (*T, error)

Khi quá trình khởi tạo có thể không thành công, hãy trả về (*T, error):

Go
import "errors"

func NewUser(name string, age int) (*User, error) {
    if name == "" {
        return nil, errors.New("nome obbligatorio")
    }
    if age < 0 {
        return nil, errors.New("eta non valida")
    }
    return &User{Name: name, Age: age}, nil
}

u, err := NewUser("Ada", 36)
if err != nil {
    // handle...
}

Đây là mẫu thành ngữ cho bất kỳ hàm tạo nào có điều kiện tiên quyết: kết nối db, xử lý tệp, cấu hình máy chủ, v.v.

Nhiều hàm tạo

Nếu bạn có nhiều "cách" để xây dựng, hãy sử dụng tên mô tả:

Go
func NewUser(name string) *User { ... }
func NewUserFromJSON(data []byte) (*User, error) { ... }
func NewAdmin() *User { ... }

Không bị quá tải trong Go: tên không rõ ràng.

Tùy chọn chức năng (cái nhìn nâng cao)

Khi có nhiều tham số tùy chọn, mẫu thành ngữ là "tùy chọn chức năng":

Go
type Option func(*Server)

func WithPort(p int) Option {
    return func(s *Server) { s.Port = p }
}

func WithTLS(cert, key string) Option {
    return func(s *Server) { s.Cert, s.Key = cert, key }
}

func NewServer(opts ...Option) *Server {
    s := &Server{Port: 8080} // default
    for _, opt := range opts {
        opt(s)
    }
    return s
}

s := NewServer(WithPort(9000), WithTLS("c.pem", "k.pem"))

Nó dài dòng hơn nghĩa đen nhưng có tỷ lệ phù hợp với nhiều tùy chọn tham số và duy trì khả năng tương thích ngược (thêm Option mới không phá vỡ người gọi hiện tại).

Khi bạn KHÔNG cần một hàm tạo

Nếu giá trị 0 của loại đã hữu ích, hãy để người gọi sử dụng nghĩa đen trực tiếp. Ví dụ chuẩn: sync.Mutex không có hàm tạo, var m sync.Mutex đã là một mutex sẵn sàng sử dụng. Tương tự cho bytes.Buffer.

Quy tắc là: "Làm cho giá trị 0 trở nên hữu ích". Nếu bạn có thể thì không hàm tạo bắt buộc.

Hãy thử nó

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

Viết hàm xuất xưởng NewUser(name string, age int) *User trả về &User{Name: name, Age: age}.

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

Sử dụng `&User{...}` theo nghĩa đen để trả về một con trỏ.

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

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

Biến Người dùng mới thành (chuỗi tên) (*Người dùng, lỗi): if name == '' return nil,errors.New('nome obbligatorio').

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

Nếu có điều kiện tiên quyết, hãy trả về (*T, lỗi) và trả về nil + lỗi khi thất bại.

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

Quiz#go.m5.l5.e3
Sẵn sàng

Quy ước thành ngữ dành cho các hàm tạo trong Go là gì?

Go
// Which signature is more idiomatic?
Tùy chọn trả lời

Tóm tắt lại

  • Không constructor: sử dụng hàm NewT (hoặc New bên trong gói cùng tên).
  • Trả về *T đối với trường hợp đơn giản; (*T, error) với điều kiện tiên quyết hoặc xác thực.
  • Khi có lỗi: trả về nil làm con trỏ và lỗi giải thích làm giá trị thứ hai.
  • Không quá tải; sử dụng tên mô tả (NewUserFromJSON, NewAdmin).
  • "Làm cho giá trị 0 trở nên hữu ích": nếu có thể thì hàm tạo là không cần thiết.
  • Mẫu nâng cao cho nhiều tham số tùy chọn: tùy chọn chức năng.