你有沒有遇過要同時處理單一物件與一群物件?就像在資料夾裡,你可以打開一個單獨的檔案,也可以打開一個資料夾,裡面可能包含了更多檔案或其他資料夾。這樣的層級結構看似複雜,但對使用者來說,我們希望能像操作單一檔案那樣簡單。這就是組合模式 Composite Pattern 的魔力所在,讓我們可以將單一物件與多個物件統一處理,創造出一個靈活且可擴展的層級架構。
什麼是組合模式?
組合模式是一種結構型設計模式,它讓你能夠像處理單一物件一樣去操作物件的集合。在這個模式中,我們可以將物件組織成樹狀結構,透過將單一物件和組合物件視為同一個介面,實現對單一物件和複合物件的統一操作。
簡單來說,組合模式解決了『如何讓樹狀結構中的物件與物件集合能被同樣對待』的問題。例如在一個UI系統中,按鈕、文字框等單一元素是葉子節點,而 Window、Panel 則是組合節點。我們希望能夠以相同的方式新增、移除和操作這些不同的元素,而不必區分它們是單一物件還是組合物件。
組合模式通常包含以下角色:
- 元件(Component):為所有具體元件和組合類定義共同的介面。
- 葉節點(Leaf):表示組合中的葉節點對象,葉節點沒有子節點。
- 組合(Composite):表示複雜元件,包含其他元件(可以是葉節點或其他複雜元件)。
- 客戶端(Client):通過元件介面與所有對象進行交互。
組合模式在圖形編輯器中的應用
讓我們來看一個圖形編輯器的例子。在圖形編輯器中,舉例:小畫家或其他類似工具,你可能會有單一的基本圖形(如圓形、矩形),也可能會有由多個基本圖形組成的複合圖形。組合模式讓我們可以使用相同的方式來操作這些基本圖形和複合圖形,無需關心它們的具體類型。
我們先定義一個圖形的基礎介面 Graphic
,讓所有圖形(單一圖形或組合圖形)都可以遵循相同的操作方式,例如:draw
跟 move
,1
2
3
4
5
6class Graphic {
public:
virtual void draw() const = 0;
virtual void move(int x, int y) = 0;
virtual ~Graphic() = default;
};
接著我們來實現單一的圖形類別,比如 Circle
和 Rectangle
,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class Circle : public Graphic {
public:
void draw() const override {
std::cout << "Drawing Circle\n";
}
void move(int x, int y) override {
std::cout << "Move the Circle to (" << x << ", " << y << ")\n";
}
};
class Rectangle : public Graphic {
public:
void draw() const override {
std::cout << "Drawing Rectangle\n";
}
void move(int x, int y) override {
std::cout << "Move the Rectangle to (" << x << ", " << y << ")\n";
}
};
現在我們實現一個組合圖形類別 CompositeGraphic
,它可以包含多個圖形,不管是單一圖形還是其他複合圖形,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class CompositeGraphic : public Graphic {
private:
std::vector<Graphic*> graphics;
public:
void add(Graphic* graphic) {
graphics.push_back(graphic);
}
void draw() const override {
for (const auto& graphic : graphics) {
graphic->draw();
}
}
void move(int x, int y) override {
std::cout << "Move graphic to (" << x << ", " << y << "):\n";
for (auto& graphic : graphics) {
graphic->move(x, y);
}
}
};
在這裡我們將建立單一的圖形物件和組合的圖形物件,並使用相同的方式來操作它們,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25int main() {
// 建立單一圖形
Circle circle;
Rectangle rectangle;
// 建立組合圖形
CompositeGraphic composite;
composite.add(&circle);
composite.add(&rectangle);
// 再次建立一個複合圖形並巢狀
CompositeGraphic complexComposite;
complexComposite.add(&composite);
complexComposite.add(&circle);
// 繪製所有圖形
std::cout << "Drawing composite graphic:\n";
complexComposite.draw();
// 移動所有圖形
std::cout << "Move composite graphic:" << std::endl;
complexComposite.move(10, 20);
return 0;
}
執行上述程式碼,我們會得到以下輸出:1
2
3
4
5
6
7
8
9
10Drawing composite graphic:
Drawing Circle
Drawing Rectangle
Drawing Circle
Move composite graphic:
Move graphic to (10, 20):
Move graphic to (10, 20):
Move the Circle to (10, 20)
Move the Rectangle to (10, 20)
Move the Circle to (10, 20)
在這個例子中,CompositeGraphic
允許我們將單一圖形和其他組合圖形一起處理,無需區分它們是單一圖形還是由多個圖形組成的組合。這樣一來整個圖形編輯器的結構變得更加靈活,讓我們能夠輕鬆管理複雜的圖形組合。
組合模式的優缺點
組合模式的最大優勢在於它讓系統結構更加簡單和靈活。無論我們處理的是單一物件還是複合物件,都可以使用同樣的介面,這降低了處理不同類型物件時的複雜性。另外組合模式讓我們可以輕鬆地擴展系統,只需新增更多類型的物件或組合物件,而不必修改現有的程式碼。
組合模式也帶來了額外的設計複雜度。當我們的物件結構變得非常複雜時,理解和管理這些層級可能會變得困難。此外對於只需要處理單一物件的情況來說,組合模式的額外靈活性可能是多餘的,反而會增加系統的負擔。
總結
組合模式讓我們能夠用統一的方式處理單一物件和複合物件。它廣泛應用於各種層級結構中,例如圖形編輯器、檔案系統、UI 元素等。雖然這個模式能夠簡化對複雜物件結構的處理,但它也可能帶來一些設計上的挑戰,因此在使用時需要謹慎考量其適用性。
當你下次面對需要同時處理單一和多個物件的情況時,組合模式可能正是解決問題的鑰匙。無論是單一物件還是複合物件,最終它們都應該能夠被簡單而一致地操作,這就是組合模式的魅力所在。