Mẫu Builder trong Go
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 80 trên 107.
Mẫu Builder tách việc tạo một đối tượng phức tạp khỏi cách biểu diễn đối tượng đó. Khác với mẫu Functional Options, Builder dùng một cấu trúc builder riêng với các phương thức cấu hình đối tượng từng bước.
Dưới đây là cách triển khai một trình dựng cho yêu cầu HTTP:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}Mỗi phương thức trả về con trỏ đến builder, cho phép bạn gọi nối tiếp các phương thức. Phương thức Build() hoàn tất quá trình xây dựng và trả về đối tượng đã tạo:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()Mẫu Builder phù hợp khi đối tượng cần trải qua nhiều bước cấu hình hoặc khi bạn muốn buộc quy trình khởi tạo tuân theo một trình tự cụ thể. Mẫu này đặc biệt hữu ích khi tạo đối tượng bất biến hoặc khi cần xác thực chính quy trình khởi tạo.
Thử thách
DễHãy cùng tạo trình soạn email bằng Mẫu Builder nhé! Email có nhiều thành phần tùy chọn như người nhận, CC, chủ đề, nội dung và tệp đính kèm, nên rất phù hợp để tạo từng bước bằng cách nối chuỗi phương thức.
Bạn sẽ sắp xếp code trong hai tệp:
email.go: Định nghĩa cấu trúc email và trình tạo.from(chuỗi)to(slice các chuỗi)cc(slice các chuỗi)subject(chuỗi)body(chuỗi)From(address string)- đặt địa chỉ người gửiTo(address string)- thêm người nhận vào slicetoCC(address string)- thêm người nhận vào sliceccSubject(subject string)- đặt dòng chủ đềBody(body string)- đặt nội dung emailmain.go: Tạo và hiển thị email bằng trình tạo.Đọc địa chỉ người gửi, sau đó đọc số lượng người nhận và địa chỉ của từng người. Tiếp theo, đọc số lượng người nhận CC và địa chỉ của từng người. Cuối cùng, đọc chủ đề và nội dung.
Dùng EmailBuilder cùng cách nối chuỗi phương thức để tạo email, rồi in phần tóm tắt.
Tạo một cấu trúc Email với các trường sau:
Tạo cấu trúc EmailBuilder chứa con trỏ đến một Email đang được tạo. Triển khai NewEmailBuilder() để trả về một trình tạo mới có Email đã khởi tạo (các slice to và cc rỗng).
Thêm các phương thức của trình tạo sau, mỗi phương thức trả về *EmailBuilder để nối chuỗi:
Thêm phương thức Build() để trả về *Email đã tạo.
Cuối cùng, thêm phương thức Summary() vào Email để trả về một chuỗi theo định dạng sau:
From: [from]
To: [comma-separated to addresses]
CC: [comma-separated cc addresses or "none"]
Subject: [subject]
Body: [body]Đầu vào sẽ có dạng như sau:
- Dòng 1: Địa chỉ người gửi
- Dòng 2: Số lượng người nhận
- Các dòng tiếp theo: Địa chỉ của từng người nhận
- Dòng tiếp theo: Số lượng người nhận CC
- Các dòng tiếp theo: Địa chỉ của từng người nhận CC
- Dòng tiếp theo: Chủ đề
- Dòng cuối: Nội dung
Ví dụ, với đầu vào:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.Đầu ra của bạn sẽ là:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.Với đầu vào:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.Đầu ra của bạn sẽ là:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.Hãy chú ý cách trình tạo cho phép bạn thêm nhiều người nhận, từng người một, thông qua các lần gọi To(), và cách mẫu này giúp quy trình tạo luôn gọn gàng, dễ đọc ngay cả khi có nhiều trường tùy chọn.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc địa chỉ người gửi
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// Đọc số lượng người nhận
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// Đọc địa chỉ của từng người nhận
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// Đọc số lượng người nhận CC
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// Đọc từng địa chỉ CC
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// Đọc tiêu đề
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// Đọc nội dung
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: Dùng EmailBuilder và chuỗi phương thức (method) để tạo email
// Bắt đầu với NewEmailBuilder(), rồi gọi nối tiếp From(), To(), CC(), Subject(), Body()
// Cuối cùng, gọi Build() để lấy Email
// TODO: In ra bản tóm tắt email bằng phương thức Summary()
}
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