Mẫu trạng thái
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 100 trên 104.
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, khiến đối tượng trông như thể đã đổi lớp. Thay vì dùng các câu lệnh điều kiện phức tạp để xử lý những trạng thái khác nhau, bạn đóng gói mỗi trạng thái thành một lớp riêng.
Mẫu này gồm một ngữ cảnh giữ tham chiếu đến trạng thái hiện tại, một giao diện trạng thái định nghĩa hành vi riêng cho từng trạng thái và các trạng thái cụ thể triển khai hành vi cho từng trạng thái:
#include <iostream>
#include <memory>
class Document; // Khai báo trước
// Giao diện trạng thái
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Ngữ cảnh
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// Các trạng thái cụ thể
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}Mỗi trạng thái xử lý hành động publish() theo cách khác nhau và chịu trách nhiệm chuyển sang trạng thái tiếp theo. Document không cần biết logic chuyển trạng thái, nó chỉ cần ủy quyền cho trạng thái hiện tại. Cách này loại bỏ các câu lệnh switch lớn và giúp việc thêm trạng thái mới trở nên đơn giản.
Hãy dùng State khi hành vi của một đối tượng phụ thuộc nhiều vào trạng thái của nó và bạn có nhiều câu lệnh điều kiện chuyển đổi dựa trên trạng thái của đối tượng.
Thử thách
DễHãy xây dựng Bộ điều khiển đèn giao thông bằng mẫu State. Bạn sẽ tạo một hệ thống trong đó đèn giao thông luân phiên qua các trạng thái khác nhau (đỏ, vàng, xanh lá), mỗi trạng thái quyết định điều gì xảy ra khi đèn chuyển trạng thái và thông báo nào được hiển thị. Đây là ví dụ kinh điển về mẫu State: hoạt động của đèn giao thông hoàn toàn phụ thuộc vào trạng thái hiện tại.
Bạn sẽ sắp xếp code trong ba tệp:
TrafficLightState.h: Định nghĩa giao diện trạng thái và tất cả các lớp trạng thái cụ thể.Tạo lớp trừu tượng
TrafficLightStatevới:- Một phương thức thuần ảo
change(TrafficLight& light)để xử lý việc chuyển sang trạng thái tiếp theo - Một phương thức thuần ảo
getColor()trả về màu hiện tại dưới dạng chuỗi - Một phương thức thuần ảo
getAction()trả về việc người lái xe nên làm gì (ví dụ: "Stop", "Caution", "Go") - Một hàm hủy ảo
Triển khai ba trạng thái cụ thể:
RedState: màu là"Red", hành động là"Stop", chuyển sang GreenYellowState: màu là"Yellow", hành động là"Caution", chuyển sang RedGreenState: màu là"Green", hành động là"Go", chuyển sang Yellow
Bạn cần khai báo trước
TrafficLightvì các trạng thái tham chiếu đến lớp này.- Một phương thức thuần ảo
TrafficLight.h: Tạo lớp ngữ cảnh để duy trì trạng thái hiện tại.Lớp
TrafficLightcần chứa mộtstd::unique_ptr<TrafficLightState>và mặc định bắt đầu ở trạng thái Red. Triển khai:setState(std::unique_ptr<TrafficLightState> newState): thay đổi trạng thái hiện tạichange(): ủy quyền cho phương thức change của trạng thái hiện tạidisplay(): in trạng thái của đèn theo định dạng:[Color]: [Action]
main.cpp: Minh họa đèn giao thông luân phiên qua các trạng thái.Đọc một đầu vào: số lần cần chuyển trạng thái (số nguyên).
Tạo một
TrafficLightvà hiển thị trạng thái ban đầu. Sau đó thực hiện số lần chuyển trạng thái đã cho, đồng thời hiển thị trạng thái sau mỗi lần chuyển.
Ví dụ, với đầu vào 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopVới đầu vào 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopHãy chú ý cách đèn giao thông luân phiên qua các trạng thái theo một quy luật dễ đoán: Đỏ → Xanh lá → Vàng → Đỏ. Mỗi trạng thái biết trạng thái nào sẽ đến tiếp theo và tự xử lý việc chuyển trạng thái của mình. Lớp TrafficLight không cần dùng câu lệnh điều kiện để xác định điều gì sẽ xảy ra. Lớp chỉ cần ủy quyền cho trạng thái hiện tại.
Tự làm thử
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Đọc số lần chuyển trạng thái
int numChanges;
cin >> numChanges;
// TODO: Tạo một đối tượng (object) TrafficLight
// TODO: Hiển thị trạng thái ban đầu
// TODO: Thực hiện số lần chuyển trạng thái đã chỉ định
// Sau mỗi lần chuyển, hiển thị trạng thái hiện tại
return 0;
}
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 về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học11Các khái niệm OOP nâng cao
Kết hợp so với kế thừaMixin qua CRTPMẫu PimplXóa kiểuLớp enum và định kiểu mạnhXử lý ngoại lệ trong OOPPhân cấp ngoại lệ tùy chỉnh14Mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổng hợpRAII như một mẫu thiết kế3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online