Chuỗi phương thức
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 81 trên 107.
Nối chuỗi phương thức (method chaining) là kỹ thuật trong đó các phương thức trả về đối tượng nhận, cho phép liên kết nhiều lời gọi phương thức trong cùng một câu lệnh. Bạn đã thấy kỹ thuật này trong mẫu Builder, nhưng đây cũng là một khái niệm độc lập, hữu ích trong nhiều tình huống.
Điều cốt yếu là trả về con trỏ đến struct từ mỗi phương thức:
type Query struct {
table string
columns []string
where string
limit int
}
func (q *Query) Select(cols ...string) *Query {
q.columns = cols
return q
}
func (q *Query) From(table string) *Query {
q.table = table
return q
}
func (q *Query) Where(condition string) *Query {
q.where = condition
return q
}
func (q *Query) Limit(n int) *Query {
q.limit = n
return q
}Giờ bạn có thể nối các lời gọi này một cách trôi chảy:
query := &Query{}
query.Select("name", "email").From("users").Where("active = true").Limit(10)Cách viết này gần giống ngôn ngữ tự nhiên hơn so với các câu lệnh riêng lẻ. Việc nối chuỗi phương thức phù hợp nhất khi các thao tác cấu hình hoặc chỉnh sửa một đối tượng mà không cần trả về giá trị nào khác. Cách này thường được dùng trong các trình tạo truy vấn, API cấu hình và thư viện kiểm thử điều kiện.
Hãy nhớ rằng mỗi phương thức phải dùng receiver là con trỏ và trả về *StructType để chuỗi gọi hoạt động đúng cách.
Thử thách
DễHãy xây dựng một trình tạo phần tử HTML linh hoạt, sử dụng cách gọi chuỗi phương thức (method chaining) để tạo thẻ HTML cùng thuộc tính và nội dung! Đây là một ứng dụng thực tế của cách gọi chuỗi phương thức: bạn sẽ tạo một API có cách viết gần giống ngôn ngữ tự nhiên khi xây dựng các phần tử HTML.
Bạn sẽ sắp xếp code trong hai tệp:
element.go: Định nghĩa trình tạo phần tử HTML với các phương thức có thể gọi nối tiếp.Tạo một cấu trúc
Elementlưu tên thẻ, một ánh xạ thuộc tính (từ chuỗi sang chuỗi) và nội dung bên trong. Xây dựng các phương thức có thể gọi nối tiếp sau đây, mỗi phương thức trả về*Element:NewElement(tag string)- hàm khởi tạo tạo một Element mới với tên thẻ được cung cấp và một ánh xạ thuộc tính đã khởi tạo (để trống)Attr(key, value string)- thêm một thuộc tính vào phần tửID(id string)- phương thức tiện ích đặt thuộc tính "id"Class(class string)- phương thức tiện ích đặt thuộc tính "class"Content(text string)- đặt nội dung bên trong của phần tử
Thêm phương thức
Render()để trả về chuỗi HTML. Các thuộc tính cần xuất hiện theo thứ tự bảng chữ cái của khóa. Định dạng như sau:<tag attr1="value1" attr2="value2">content</tag>Nếu không có thuộc tính nào, chỉ cần tạo
<tag>content</tag>. Nếu không có nội dung, hãy tạo<tag attr="value"></tag>.main.go: Tạo các phần tử HTML bằng cách gọi chuỗi phương thức.Đọc tên thẻ, sau đó đọc số lượng thao tác cần thực hiện. Mỗi thao tác thuộc một trong các loại sau:
idtheo sau là giá trị IDclasstheo sau là giá trị classattrtheo sau là tên và giá trị thuộc tính (hai dòng)contenttheo sau là nội dung
Gọi nối tiếp tất cả thao tác một cách liền mạch, sau đó in HTML đã tạo.
Đầu vào sẽ có dạng như sau:
- Dòng 1: Tên thẻ
- Dòng 2: Số lượng thao tác
- Các dòng tiếp theo: Loại thao tác và (các) giá trị
Ví dụ, với đầu vào:
div
3
id
container
class
main-content
content
Hello WorldĐầu ra sẽ là:
<div class="main-content" id="container">Hello World</div>Với đầu vào:
a
3
attr
href
https://example.com
attr
target
_blank
content
Click hereĐầu ra sẽ là:
<a href="https://example.com" target="_blank">Click here</a>Với đầu vào:
input
2
attr
type
text
attr
placeholder
Enter nameĐầu ra sẽ là:
<input placeholder="Enter name" type="text"></input>Với đầu vào:
p
1
content
Simple paragraphĐầu ra sẽ là:
<p>Simple paragraph</p>Hãy chú ý cách gọi chuỗi phương thức giúp bạn xây dựng phần tử từng bước một cách dễ đọc. Mỗi phương thức thay đổi phần tử rồi trả về phần tử đó, nhờ vậy bạn có thể gọi nối tiếp phương thức tiếp theo.
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc tên thẻ
scanner.Scan()
tagName := scanner.Text()
// Đọc số lượng thao tác
scanner.Scan()
numOps, _ := strconv.Atoi(scanner.Text())
// Tạo phần tử
element := NewElement(tagName)
// Xử lý từng thao tác
for i := 0; i < numOps; i++ {
scanner.Scan()
operation := scanner.Text()
// TODO: Dựa vào loại thao tác, nối chuỗi phương thức phù hợp
// Các thao tác có thể là: "id", "class", "attr" hoặc "content"
// - Với "id": đọc dòng tiếp theo làm giá trị ID
// - Với "class": đọc dòng tiếp theo làm giá trị lớp
// - Với "attr": đọc HAI dòng tiếp theo (tên thuộc tính, rồi đến giá trị)
// - Với "content": đọc dòng tiếp theo làm văn bản nội dung
// Dùng cách nối chuỗi phương thức để tạo phần tử một cách liền mạch
_ = operation // Xóa dòng này khi triển khai
}
// In ra HTML sau khi kết xuất
fmt.Println(element.Render())
}
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