Mẫu trạng thái
Thuộc phần Lập trình hướng đối tượng trong hành trình Swift của Coddy. Bài học 49 trên 57.
Mẫu state cho phép một đối tượng thay đổi hành vi khi trạng thái bên trong thay đổi. Mỗi trạng thái là một kiểu riêng, quyết định điều gì xảy ra và trạng thái nào sẽ đến tiếp theo:
protocol LightState {
var name: String { get }
func next() -> any LightState
}
struct Red: LightState { var name: String { "red" }; func next() -> any LightState { Green() } }
struct Green: LightState { var name: String { "green" }; func next() -> any LightState { Yellow() } }
struct Yellow: LightState { var name: String { "yellow" }; func next() -> any LightState { Red() } }
var light: any LightState = Red()
for _ in 1...4 {
print(light.name)
light = light.next()
}Đầu ra:
red
green
yellow
redĐối tượng sở hữu trạng thái chuyển các yêu cầu đến trạng thái hiện tại. Mỗi trạng thái xử lý một sự kiện theo cách riêng, còn những trạng thái không cho phép các hành động trái phép sẽ từ chối chúng:
protocol OrderState {
var name: String { get }
func pay() -> (any OrderState)?
func ship() -> (any OrderState)?
}
struct New: OrderState { var name: String { "new" }; func pay() -> (any OrderState)? { Paid() }; func ship() -> (any OrderState)? { nil } }
struct Paid: OrderState { var name: String { "paid" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { Shipped() } }
struct Shipped: OrderState { var name: String { "shipped" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { nil } }
final class Order {
private(set) var state: any OrderState = New()
func handle(_ event: String) -> String {
guard let next = event == "pay" ? state.pay() : state.ship() else { return "cannot \(event) when \(state.name)" }
state = next
return "now \(state.name)"
}
}
let order = Order()
for e in ["ship", "pay", "pay", "ship"] { print(order.handle(e)) }Đầu ra:
cannot ship when new
now paid
cannot pay when paid
now shippedVới một số lượng nhỏ trạng thái không có dữ liệu riêng, enum cùng với switch thường diễn đạt cùng một ý tưởng gọn hơn, đồng thời trình biên dịch kiểm tra để bảo đảm mọi trạng thái đều được xử lý:
enum Door {
case open, closed, locked
func handle(_ action: String) -> Door? {
switch (self, action) {
case (.closed, "open"): return .open
case (.open, "close"): return .closed
case (.closed, "lock"): return .locked
case (.locked, "unlock"): return .closed
default: return nil
}
}
}
var door = Door.closed
for a in ["lock", "open", "unlock", "open"] {
if let next = door.handle(a) { door = next; print("\(a) -> \(door)") } else { print("cannot \(a)") }
}Đầu ra:
lock -> locked
cannot open
unlock -> closed
open -> openKiểu trạng thái phát huy tác dụng khi mỗi trạng thái có nhiều hành vi hoặc dữ liệu riêng. Dù thế nào, các quy tắc cho từng trạng thái đều nằm ở một chỗ thay vì rải rác trong các câu lệnh kiểm tra if khắp đối tượng.
// Phân tán: mỗi phương thức (method) đều kiểm tra trạng thái
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// Mẫu trạng thái: mỗi trạng thái tự xử lý
state = state.ship() ?? stateThử thách
DễMô hình hóa máy bán hàng tự động bằng trạng thái enum Vending với các trường hợp idle, hasCoin(Int) và soldOut. handle(_ event:) trả về trạng thái tiếp theo hoặc nil nếu sự kiện không được phép: coin <n> trong idle chuyển sang hasCoin(n), còn trong hasCoin thì cộng thêm vào số tiền; buy trong hasCoin với số tiền ít nhất là 150 chuyển sang idle (giao dịch thành công); refund trong hasCoin chuyển sang idle; empty ở bất kỳ trạng thái nào cũng chuyển sang soldOut; refill trong soldOut chuyển sang idle.
Code được cung cấp áp dụng các sự kiện theo thứ tự và in ra trạng thái sau mỗi sự kiện (idle, coin 100, sold out) hoặc not allowed: <event>.
Viết code trong Vending.swift. main.swift chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.
Tự làm thử
// Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
var input: [String] = []
while let line = readLine() { input.append(line) }
var machine = Vending.idle
for event in input {
if let next = machine.handle(event) {
machine = next
print(machine.label)
} else {
print("not allowed: \(event)")
}
}
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
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpPhương thức và selfHàm khởi tạoLớp là tham chiếuÔn tập: Thẻ thư viện2Struct và kiểu giá trị
StructNgữ nghĩa giá trịPhương thức thay đổiStruct hay classÔn tập: mặt hàng trong kho5Kế thừa
Lớp conGhi đè phương thứcHàm khởi tạo và superfinal và hệ phân cấpÔn tập: hệ phân cấp nhân viên8Các giao thức tiêu chuẩn
CustomStringConvertibleEquatableComparableHashableÔn tập: Sắp xếp tùy chỉnhTự luyện tập: Playground Swift