Mẫu tùy chọn hàm
Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 79 trên 107.
Khi tạo struct có nhiều trường cấu hình tùy chọn, các hàm khởi tạo (constructor) có thể trở nên cồng kềnh. Mẫu tùy chọn hàm (Functional Options Pattern) giải quyết vấn đề này một cách tinh tế bằng cách dùng hàm (function) để cấu hình đối tượng (object).
Hãy xét một cấu trúc máy chủ có nhiều thiết lập tùy chọn. Thay vì dùng một hàm khởi tạo (constructor) với nhiều tham số, ta định nghĩa một kiểu tùy chọn và các hàm (function) trả về tùy chọn:
type Server struct {
host string
port int
timeout int
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout int) Option {
return func(s *Server) {
s.timeout = timeout
}
}Hàm khởi tạo nhận một danh sách tùy chọn có số lượng linh hoạt và áp dụng từng tùy chọn:
func NewServer(host string, opts ...Option) *Server {
s := &Server{
host: host,
port: 8080, // mặc định
timeout: 30, // mặc định
}
for _, opt := range opts {
opt(s)
}
return s
}Giờ đây, việc tạo máy chủ trở nên dễ đọc và linh hoạt:
// Dùng các giá trị mặc định
s1 := NewServer("localhost")
// Tùy chỉnh các tùy chọn cụ thể
s2 := NewServer("localhost", WithPort(3000), WithTimeout(60))Mẫu này nổi bật vì bên gọi chỉ cần chỉ định những gì họ cần, các giá trị mặc định được trình bày rõ ràng ở một nơi, và việc thêm tùy chọn mới không làm hỏng code hiện có. Mẫu này được dùng rộng rãi trong các thư viện Go như grpc và zap.
Thử thách
DễHãy xây dựng một kết nối cơ sở dữ liệu có thể cấu hình bằng Mẫu tùy chọn hàm! Mẫu này rất hữu ích khi bạn có nhiều thiết lập tùy chọn với giá trị mặc định hợp lý, đúng như những gì một kết nối cơ sở dữ liệu cần.
Bạn sẽ sắp xếp code trong hai tệp:
database.go: Định nghĩa kiểu kết nối cơ sở dữ liệu và các hàm tùy chọn.Tạo một cấu trúc
DBConnectionvới các trường không được xuất sau:host(chuỗi)port(số nguyên)username(chuỗi)password(chuỗi)maxConnections(số nguyên)timeout(số nguyên), tính bằng giây
Định nghĩa kiểu
Optionlà một hàm thay đổi*DBConnection.Tạo các hàm tùy chọn sau, mỗi hàm trả về một
Option:WithPort(port int)WithCredentials(username, password string)WithMaxConnections(max int)WithTimeout(seconds int)
Tạo hàm khởi tạo
NewDBConnection(host string, opts ...Option)trả về một*DBConnection. Đặt các giá trị mặc định sau: port là5432, username là"admin", password là"secret", maxConnections là10, timeout là30. Áp dụng tất cả tùy chọn được cung cấp sau khi đặt giá trị mặc định.Thêm phương thức
ConnectionString()để trả về thông tin kết nối theo định dạng sau:[username]:[password]@[host]:[port] (max:[maxConnections], timeout:[timeout]s)main.go: Tạo kết nối cơ sở dữ liệu với nhiều cấu hình khác nhau.Đọc host, sau đó đọc số lượng tùy chọn cần áp dụng. Với mỗi tùy chọn, hãy đọc loại tùy chọn và giá trị tương ứng:
porttheo sau là số cổngcredentialstheo sau là username và password (hai dòng)maxconntheo sau là số lượng kết nối tối đatimeouttheo sau là thời gian chờ tính bằng giây
Tạo một
DBConnectionvới các tùy chọn đã chỉ định và in chuỗi kết nối ra.
Đầu vào sẽ có định dạng sau:
- Dòng 1: Tên host
- Dòng 2: Số lượng tùy chọn
- Các dòng tiếp theo: Loại tùy chọn và giá trị tương ứng
Ví dụ, với đầu vào:
localhost
0Đầu ra sẽ là:
admin:secret@localhost:5432 (max:10, timeout:30s)Với đầu vào:
db.example.com
2
port
3306
credentials
root
mypasswordĐầu ra sẽ là:
root:mypassword@db.example.com:3306 (max:10, timeout:30s)Với đầu vào:
production.server
4
port
5433
credentials
dbuser
securepass123
maxconn
50
timeout
60Đầu ra sẽ là:
dbuser:securepass123@production.server:5433 (max:50, timeout:60s)Lưu ý rằng bên gọi chỉ cần chỉ định những tùy chọn họ cần. Các thiết lập không được chỉ định sẽ dùng giá trị mặc định hợp lý. Đó chính là điểm tinh tế của Mẫu tùy chọn hàm!
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc host
host, _ := reader.ReadString('\n')
host = strings.TrimSpace(host)
// Đọc số lượng tùy chọn
numOptionsStr, _ := reader.ReadString('\n')
numOptions, _ := strconv.Atoi(strings.TrimSpace(numOptionsStr))
// Thu thập các tùy chọn
var opts []Option
for i := 0; i < numOptions; i++ {
optionType, _ := reader.ReadString('\n')
optionType = strings.TrimSpace(optionType)
switch optionType {
case "port":
portStr, _ := reader.ReadString('\n')
port, _ := strconv.Atoi(strings.TrimSpace(portStr))
// TODO: Thêm tùy chọn WithPort vào opts
_ = port // Xóa dòng này khi triển khai
case "credentials":
username, _ := reader.ReadString('\n')
username = strings.TrimSpace(username)
password, _ := reader.ReadString('\n')
password = strings.TrimSpace(password)
// TODO: Thêm tùy chọn WithCredentials vào opts
_, _ = username, password // Xóa dòng này khi triển khai
case "maxconn":
maxStr, _ := reader.ReadString('\n')
maxConn, _ := strconv.Atoi(strings.TrimSpace(maxStr))
// TODO: Thêm tùy chọn WithMaxConnections vào opts
_ = maxConn // Xóa dòng này khi triển khai
case "timeout":
timeoutStr, _ := reader.ReadString('\n')
timeout, _ := strconv.Atoi(strings.TrimSpace(timeoutStr))
// TODO: Thêm tùy chọn WithTimeout vào opts
_ = timeout // Xóa dòng này khi triển khai
}
}
// TODO: Tạo DBConnection bằng NewDBConnection với host và opts
// TODO: In ra chuỗi kết nối bằng phương thức (method) ConnectionString()
_ = opts // Xóa dòng này khi triển khai
fmt.Println("TODO: Print connection string here")
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker pool12Các khái niệm OOP nâng cao
Mẫu tùy chọn hàmMẫu Builder trong GoChuỗi phương thứcBí danh kiểu dữ liệu và định nghĩa kiểuCơ bản về reflectionTổng quan về tạo codeTự luyện tập: Trình biên dịch Go online