Mẫu State
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 95 trên 107.
Mẫu trạng thái (State pattern) cho phép một đối tượng thay đổi hành vi khi trạng thái bên trong thay đổi, khiến đối tượng trông như thể đã đổi lớp. Trong khi Template Method kiểm soát các bước của thuật toán, State đóng gói hành vi riêng theo từng trạng thái vào các đối tượng riêng biệt và ủy quyền cho trạng thái hiện tại.
Trong Go, chúng ta định nghĩa một giao diện trạng thái và tạo các trạng thái cụ thể triển khai những hành vi khác nhau:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}Mỗi trạng thái quyết định điều gì sẽ xảy ra và trạng thái nào sẽ tiếp theo:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}Cùng một lời gọi phương thức tạo ra các kết quả khác nhau tùy thuộc vào trạng thái hiện tại:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Bản nháp đã gửi để kiểm duyệt
fmt.Println(doc.Publish()) // Đã duyệt, hiện đã xuất bản
fmt.Println(doc.Publish()) // Đã xuất bản rồiMẫu State phù hợp với các đối tượng có những chế độ hoạt động riêng biệt, chẳng hạn như quy trình xử lý đơn hàng, thành phần giao diện người dùng hoặc bộ xử lý kết nối, trong đó hành vi hoàn toàn phụ thuộc vào trạng thái hiện tại.
Thử thách
DễHãy xây dựng hệ thống hỗ trợ vé bằng mẫu State! Bạn sẽ tạo một vé hỗ trợ trải qua các giai đoạn khác nhau, từ khi được mở, đến khi được xử lý, rồi được giải quyết. Mỗi trạng thái sẽ quyết định những hành động nào có thể thực hiện và điều gì xảy ra tiếp theo.
Bạn sẽ sắp xếp code trong ba tệp:
state.go: Định nghĩa interface trạng thái và các trạng thái cụ thể đại diện cho từng giai đoạn trong vòng đời của một vé.Tạo interface
TicketStatevới phương thứcHandle(t *Ticket) stringđể xử lý vé và có thể chuyển vé sang trạng thái tiếp theo.Triển khai ba trạng thái:
OpenState: khi được xử lý, chuyển vé sangInProgressStatevà trả vềTicket opened, assigning to support teamInProgressState: khi được xử lý, chuyển vé sangResolvedStatevà trả vềWorking on ticket, issue resolvedResolvedState: khi được xử lý, giữ nguyên trạng thái và trả vềTicket already resolved
ticket.go: Tạo struct vé lưu trạng thái hiện tại và ủy quyền hành vi cho trạng thái đó.Tạo struct
Ticketvới trườngID(string) và trườngstate(TicketState). Thêm các phương thức sau:SetState(s TicketState)để thay đổi trạng thái hiện tại của véProcess() stringđể ủy quyền cho phương thức Handle của trạng thái hiện tại
Tạo hàm khởi tạo
NewTicket(id string) *Tickettrả về một vé bắt đầu ở trạng tháiOpenState.main.go: Minh họa cách cùng một hành động tạo ra các kết quả khác nhau tùy theo trạng thái hiện tại của vé.Đọc ID của vé và số lần cần xử lý vé. Tạo vé mới với ID đó, sau đó gọi
Process()số lần đã chỉ định, in từng kết quả trên một dòng riêng.
Đầu vào sẽ có định dạng sau:
- Dòng 1: ID vé
- Dòng 2: Số lần xử lý vé
Ví dụ, với đầu vào:
TKT-001
3Đầu ra sẽ là:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedVới đầu vào:
TKT-500
5Đầu ra sẽ là:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedVới đầu vào:
ISSUE-42
1Đầu ra sẽ là:
Ticket opened, assigning to support teamHãy chú ý: gọi Process() trên cùng một vé sẽ tạo ra kết quả khác nhau mỗi lần, vì hành vi của vé thay đổi khi vé chuyển qua các trạng thái. Khi đã được giải quyết, vé vẫn ở trạng thái đó dù bạn xử lý bao nhiêu lần. Đối tượng vé có vẻ như thay đổi hành vi, nhưng thực ra nó đang ủy quyền cho các đối tượng trạng thái khác nhau!
Tự làm thử
package main
import (
"fmt"
)
func main() {
// Đọc đầu vào
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: Tạo ticket mới với ID đã cho
// TODO: Xử lý ticket theo số lần đã chỉ định
// và in mỗi kết quả trên một dòng riêng
}
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ệp11Thư viện chuẩn và OOP
io.Reader và io.Writersort.Interfaceinterface fmt.Stringerencoding/json với structinterface http.HandlerÔn tập: mô hình REST API14Mẫu thiết kế phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMiddleware dưới dạng Decorator3Con 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 poolTự luyện tập: Trình biên dịch Go online