設計模式之組合(Composite)模式
在現實生活中,存在很多“部分-整體”的關系,例如,大學中的部門與學院、總公司中的部門與分公司、學習用品中的書與書包、生活用品中的衣服與衣櫃以及廚房中的鍋碗瓢盆等。在軟體開發中也是這樣,例如,檔案系統中的檔案與檔案夾、窗體程式中的簡單控件與容器控件等。對這些簡單對象與複合對象的處理,如果用組合模式來實作會很友善。
定義與特點
組合(Composite)模式的定義:有時又叫作部分-整體模式,它是一種将對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關系,使使用者對單個對象群組合對象具有一緻的通路性。
組合模式的主要優點有:
- 組合模式使得用戶端代碼可以一緻地處理單個對象群組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了用戶端代碼;
- 更容易在組合體内加入新的對象,用戶端不會因為加入了新的對象而更改源代碼,滿足“開閉原則”;
其主要缺點是:
- 設計較複雜,用戶端需要花更多時間理清類之間的層次關系;
- 不容易限制容器中的構件;
- 不容易用繼承的方法來增加構件的新功能;
結構與實作
組合模式的結構不是很複雜,下面對它的結構和實作進行分析。
結構
組合模式包含以下主要角色。
- 抽象構件(Component)角色:它的主要作用是為樹葉構件和樹枝構件聲明公共接口,并實作它們的預設行為。在透明式的組合模式中抽象構件還聲明通路和管理子類的接口;在安全式的組合模式中不聲明通路和管理子類的接口,管理工作由樹枝構件完成。
- 樹葉構件(Leaf)角色:是組合中的葉節點對象,它沒有子節點,用于實作抽象構件角色中聲明的公共接口。
- 樹枝構件(Composite)角色:是組合中的分支節點對象,它有子節點。它實作了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
組合模式分為透明式的組合模式和安全式的組合模式。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csAza650dJhlWzY0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2cjM2EjNykTMzIjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
(2) 安全方式:在該方式中,将管理子構件的方法移到樹枝構件中,抽象構件和樹葉構件沒有對子對象的管理方法,這樣就避免了上一種方式的安全性問題,但由于葉子和分支有不同的接口,用戶端在調用時要知道樹葉對象和樹枝對象的存在,是以失去了透明性。其結構圖如圖 2 所示。
實作
假如要通路集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其對應的樹狀圖如圖 3 所示。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-ZvBpsfdi-1592911541432)(F:\個人筆記\設計模式截圖\組合模式3.png)]
下面給出透明式的組合模式的實作代碼,與安全式的組合模式的實作代碼類似,隻要對其做簡單修改就可以了。
package com.design.pattern.structuralPattern.compositePattern;
import java.util.ArrayList;
public class CompositePattern {
public static void main(String[] args) {
Component c0 = new Composite();
Component c1 = new Composite();
Component leaf1 = new Leaf("1");
Component leaf2 = new Leaf("2");
Component leaf3 = new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
//抽象構件
interface Component{
public void add(Component component);
public void remove(Component component);
public Component getChild(int i);
public void operation();
}
//樹葉構件
class Leaf implements Component{
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void add(Component component) {
}
@Override
public void remove(Component component) {
}
@Override
public Component getChild(int i) {
return null;
}
@Override
public void operation() {
System.out.println("樹葉"+name+"被通路!");
}
}
//樹枝構件
class Composite implements Component{
private ArrayList<Component> children = new ArrayList<>();
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public Component getChild(int i) {
return children.get(i);
}
@Override
public void operation() {
for (Component component : children){
component.operation();
}
}
}
執行個體
【例1】用組合模式實作當使用者在商店購物後,顯示其所選商品資訊,并計算所選商品總價的功能。
說明:假如李先生到韶關“天街e角”生活用品店購物,用 1 個紅色小袋子裝了 2 包婺源特産(單價 7.9 元)、1 張婺源地圖(單價 9.9 元);用 1 個白色小袋子裝了 2 包韶關香藉(單價 68 元)和 3 包韶關紅茶(單價 180 元);用 1 個中袋子裝了前面的紅色小袋子和 1 個景德鎮瓷器(單價 380 元);用 1 個大袋子裝了前面的中袋子、白色小袋子和 1 雙李甯牌運動鞋(單價 198 元)。
最後“大袋子”中的内容有:{1 雙李甯牌運動鞋(單價 198 元)、白色小袋子{2 包韶關香菇(單價 68 元)、3 包韶關紅茶(單價 180 元)}、中袋子{1 個景德鎮瓷器(單價 380 元)、紅色小袋子{2 包婺源特産(單價 7.9 元)、1 張婺源地圖(單價 9.9 元)}}},現在要求程式設計顯示李先生放在大袋子中的所有商品資訊并計算要支付的總價。
本執行個體可按安全組合模式設計,其結構圖如圖 4 所示。
程式代碼如下:
package com.design.pattern.structuralPattern.compositePattern;
import java.util.ArrayList;
public class ShoppingTest {
public static void main(String[] args) {
float s = 0;
Bags bigBag,mediumBag,smallRedBag,smallWhiteBag;
Goods sp;
bigBag = new Bags("大袋子");
mediumBag = new Bags("中袋子");
smallRedBag = new Bags("紅色小袋子");
smallWhiteBag = new Bags("白色小袋子");
sp = new Goods("婺源特産",2,7.9f);
smallRedBag.add(sp);
sp=new Goods("婺源地圖",1,9.9f);
smallRedBag.add(sp);
sp=new Goods("韶關香菇",2,68);
smallWhiteBag.add(sp);
sp=new Goods("韶關紅茶",3,180);
smallWhiteBag.add(sp);
sp=new Goods("景德鎮瓷器",1,380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp=new Goods("李甯牌運動鞋",1,198);
bigBag.add(sp);
bigBag.add(smallWhiteBag);
bigBag.add(mediumBag);
System.out.println("您選購的商品有:");
bigBag.show();
s=bigBag.calculation();
System.out.println("要支付的總價是:"+s+"元");
}
}
//抽象構件:物品
interface Articles{
public float calculation();
public void show();
}
//樹葉構件:商品
class Goods implements Articles{
private String name;
private int quantity;
private float unitPrice;
public Goods(String name, int quantity, float unitPrice) {
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
@Override
public float calculation() {
return quantity*unitPrice;
}
@Override
public void show() {
System.out.println(name+"(數量:"+quantity+",單價:"+unitPrice+"元"+")");
}
}
//樹枝構件:袋子
class Bags implements Articles{
private String name;
private ArrayList<Articles> bags = new ArrayList<>();
public Bags(String name) {
this.name = name;
}
public void add(Articles articles){
bags.add(articles);
}
public void remove(Articles articles){
bags.remove(articles);
}
public Articles getChild(int i){
return bags.get(i);
}
@Override
public float calculation() {
float s = 0;
for (Articles articles : bags){
s+=articles.calculation();
}
return s;
}
@Override
public void show() {
for (Articles articles : bags){
articles.show();
}
}
}
應用場景
前面分析了組合模式的結構與特點,下面分析它适用的以下應用場景。
- 在需要表示一個對象整體與部分的層次結構的場合。
- 要求對使用者隐藏組合對象與單個對象的不同,使用者可以用統一的接口使用組合結構中的所有對象的場合。
組合模式的擴充
如果對前面介紹的組合模式中的樹葉節點和樹枝節點進行抽象,也就是說樹葉節點和樹枝節點還有子節點,這時組合模式就擴充成複雜的組合模式了,如
Java AWT/Swing
中的簡單元件
JTextComponent
有子類
JTextField
、
JTextArea
,容器元件
Container
也有子類
Window
、
Panel
。複雜的組合模式的結構圖如圖 5 所示。