《C++ 沉思錄》課堂練習要求:圖片支援加邊框、水平/垂直拼接,並能疊加多層裝飾。教科書級答案動用裝飾模式、組合模式、介面繼承、虛函數等重型武器。作者認為:**消息驅動(Message Passing)才是本質解法**,無需繼承、虛函數、任何設計模式,幾十行代碼徹底搞定。
enum { PM_WIDTH, PM_HEIGHT, PM_ROW };
typedef int (*ProcMsg)(void* obj, int msg, void* p1, void* p2);
struct PictureMsg {
void* obj; // 承載具體實現的 this 指標
ProcMsg proc; // 消息處理函數
// 輔助方法
int GetWidth() { return proc(obj, PM_WIDTH, 0, 0); }
int GetHeight() { return proc(obj, PM_HEIGHT, 0, 0); }
int GetRow(int row, std::string& out) {
return proc(obj, PM_ROW, (void*)row, (void*)&out);
}
};
class CPicture : public PictureMsg {
const char** data; int rows, width;
static int HandleMsg(void* obj, int msg, void* p1, void* p2) {
CPicture* self = (CPicture*)obj;
switch (msg) {
case PM_WIDTH: return self->width;
case PM_HEIGHT: return self->rows;
case PM_ROW: {
int r = (int)p1; std::string& out = *(std::string*)p2;
if (r >= self->rows) return 0;
out = self->data[r];
out.append(self->width - out.length(), ' '); // 補齊寬度
return 0;
}
}
return 0;
}
public:
CPicture(const char* d[], int n) : data(d), rows(n), width(0) {
for (int i=0;i
class CFrameDecorater {
PictureMsg prev; // 被裝飾對象的介面副本
static int HandleMsg(void* obj, int msg, void* p1, void* p2) {
CFrameDecorater* self = (CFrameDecorater*)obj;
PictureMsg& p = self->prev;
switch (msg) {
case PM_WIDTH: return p.GetWidth() + 2;
case PM_HEIGHT: return p.GetHeight() + 2;
case PM_ROW: {
int r = (int)p1; std::string& out = *(std::string*)p2;
if (r == 0 || r == p.GetHeight()+1) { // 頂/底邊框
out = "+" + std::string(p.GetWidth(), '-') + "+";
} else { // 中間行
std::string mid; p.GetRow(r-1, mid);
out = "|" + mid + "|";
}
return 0;
}
}
return 0;
}
public:
CFrameDecorater(PictureMsg& imp) : prev(imp) {
imp.obj = this; imp.proc = HandleMsg; // 劫持介面
}
};
class CHorseDecorater {
PictureMsg left, right;
static void GetRowSafe(PictureMsg& p, int r, std::string& out) {
if (r < p.GetHeight()) p.GetRow(r, out);
else out = std::string(p.GetWidth(), ' ');
}
static int HandleMsg(void* obj, int msg, void* p1, void* p2) {
CHorseDecorater* self = (CHorseDecorater*)obj;
switch (msg) {
case PM_WIDTH: return self->left.GetWidth() + self->right.GetWidth();
case PM_HEIGHT: return max(self->left.GetHeight(), self->right.GetHeight());
case PM_ROW: {
int r = (int)p1; std::string& out = *(std::string*)p2;
std::string l, rstr;
GetRowSafe(self->left, r, l);
GetRowSafe(self->right, r, rstr);
out = l + rstr;
return 0;
}
}
return 0;
}
public:
CHorseDecorater(PictureMsg& l, PictureMsg& r) : left(l), right(r) {
l.obj = this; l.proc = HandleMsg; // 劫持左介面作為結果介面
}
};
std::ostream& operator<<(std::ostream& os, PictureMsg& msg) {
for (int r=0; r
int main() {
const char* pic1[] = {"Paris", "in the", "Spring", "HaHa"};
CPicture p1(pic1, 3); // 基礎圖片
CFrameDecorater f1(p1); // 加邊框
CFrameDecorater f2(p1); // 再加一層邊框
CPicture p2(pic1, 4); // 另一張圖
CHorseDecorater h(p1, p2); // 水平拼接
CFrameDecorater fh(p1); // 給拼接結果加框
CHorseDecorater h2(p2, p1); // 反向拼接
CFrameDecorater f3(p2);
CHorseDecorater h3(p1, p2);
std::cout << p1; // 輸出最終結果(p1 介面已被多次劫持,指向最後裝飾層)
return 0;
}
每次建立裝飾器時:
`decorator.constructor(prev_interface) { prev_interface.obj = this; prev_interface.proc = MyHandler; }`
將**被裝飾者的介面結構體**直接改寫,使其指向新裝飾層處理函數。原圖片物件 `p1` **完全不知情**,仍保持原樣,但其 `PictureMsg` 介面已被重導向至最外層裝飾鏈。這就是「不管八風亂起,我自巋然不動」。
| 維度 | 消息驅動 | 裝飾/組合模式 |
|---|---|---|
| 代碼量 | ~100 行 | ~300+ 行 |
| 繼承/虛函數 | 無 | 大量 |
| 間接層 | 單層函數指標調用 | 多層虛函數表查找 |
| 組合靈活性 | 任意動態重組 | 靜態類型綁定 |
| 本質 | 攔截器鏈 | 物件包裝樹 |
**消息驅動即設計模式的本質實現**。Windows 視窗程序 `WndProc(hwnd, msg, wParam, lParam)` 早已證明:單一消息分發函數足以表達所有 GUI 行為變化。C++ 沉思錄練習題的「另解」,不過是將這一經典模式應用於領域模型罷了。