狀態模式是行為型設計模式之一,允許物件在內部狀態改變時改變其行為。本文透過實作一個 HTML 解析器,分享狀態模式如何優雅地解決複雜狀態轉換問題,並探討其優缺點與實作細節。
HTML 解析本質上是字元流的狀態機處理。字元類型包含:`<`、`>`、`/`、`=`、字母、數字、底線、漢字等。解析過程需識別標籤名、屬性名、屬性值、標籤值等狀態,且狀態間轉換頻繁。
若使用巢狀 switch-case 處理(外層判斷字元、內層判斷當前狀態),程式碼會極度膨脹且難以維護:
do {
char ch = *itChar;
switch (ch) {
case '<':
switch (state) {
case START_STATE: /* 處理開始標籤 */ break;
case TAG_VALUE_STATE: /* 處理標籤值 */ break;
// ... 更多狀態
}
break;
case '>':
switch (state) {
case LEFT_TAG_STATE: /* 處理左標籤 */ break;
case RIGHT_TAG_STATE: /* 處理右標籤 */ break;
}
break;
// ... 更多字元判斷
}
} while (!end);
狀態模式核心思想:為每個狀態建立獨立類別,將狀態相關行為封裝其中,透過 Context 切換狀態物件。
![]()
State*),提供 Request() 觸發處理,並允許狀態物件呼叫 ChangeState() 切換狀態。Handle(Context*) 純虛函數,並提供 ChangeState() 協助切換。Handle() 中執行該狀態邏輯,並在需要時呼叫 ChangeState() 切換至下一狀態。// State.h
class Context; // 前置宣告
class State {
public:
virtual ~State() {}
virtual void Handle(Context* con) = 0;
protected:
bool ChangeState(Context* con, State* st);
};
class ConcreteStateA : public State {
public:
void Handle(Context* con) override {
cout << "處理狀態 A" << endl;
ChangeState(con, new ConcreteStateB()); // 切換到狀態 B
}
};
class ConcreteStateB : public State {
public:
void Handle(Context* con) override {
cout << "處理狀態 B" << endl;
ChangeState(con, new ConcreteStateA()); // 切換回狀態 A
}
};
// Context.h
class Context {
public:
Context(State* state = nullptr) : _state(state) {}
~Context() { delete _state; }
void Request() { if (_state) _state->Handle(this); }
bool ChangeState(State* state) {
delete _state; // 釋放舊狀態
_state = state;
return true;
}
private:
State* _state;
friend class State; // 允許 State 存取私有 ChangeState
};
實務上需考慮:
Request() 前手動呼叫 Entry(),並在 ChangeState() 中呼叫 Exit()。Entry(),解構子呼叫 Exit()(注意:下一狀態 Entry 會在上一狀態 Exit 前執行)。ChangeState(stateID) 內先 delete 舊狀態(觸發 Exit),再 new 新狀態(觸發 Entry),順序正確且記憶體安全。狀態模式將「狀態判斷邏輯」分散到各具體狀態類別中,消除了龐大的 switch-case,符合開放封閉原則(新增狀態只需新增類別)。但也帶來類別數量增加的代價。適用場景:
HTML 解析器僅是狀態模式的應用例之一。實務中常結合其他模式(如工廠、單例、策略)協同運作,提升程式碼品質與可維護性。