Menu
CoddyTech

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ồi

Mẫ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.

challenge icon

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 TicketState với phương thức Handle(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é sang InProgressState và trả về Ticket opened, assigning to support team
    • InProgressState: khi được xử lý, chuyển vé sang ResolvedState và trả về Working on ticket, issue resolved
    • ResolvedState: 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 Ticket với trường ID (string) và trường state (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) *Ticket trả về một vé bắt đầu ở trạng thái OpenState.

  • 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 resolved

Vớ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 resolved

Với đầu vào:

ISSUE-42
1

Đầu ra sẽ là:

Ticket opened, assigning to support team

Hã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
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Go online