Menu
CoddyTech

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.

challenge icon

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 TrafficLightState vớ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 Green
    • YellowState: màu là "Yellow", hành động là "Caution", chuyển sang Red
    • GreenState: màu là "Green", hành động là "Go", chuyển sang Yellow

    Bạn cần khai báo trước TrafficLight vì các trạng thái tham chiếu đến lớp này.

  • TrafficLight.h: Tạo lớp ngữ cảnh để duy trì trạng thái hiện tại.

    Lớp TrafficLight cần chứa một std::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ại
    • change(): ủy quyền cho phương thức change của trạng thái hiện tại
    • display(): 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 TrafficLight và 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: Stop

Với đầu vào 6:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

Hã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;
}
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 C++ online