之前做一個(gè)需求,設(shè)計(jì)一個(gè)設(shè)置程序,設(shè)置程序中包含不同的頁面,頁面中又有不同的分組,每個(gè)分組下面又有不同的項(xiàng),每個(gè)項(xiàng)包含不同的控件。
當(dāng)用戶點(diǎn)擊保存的時(shí)候,需要將所有的配置,都映射并保存在配置文件中。
之前做的時(shí)候,是將每個(gè)控件都綁定一個(gè)映射項(xiàng),然后遍歷所有的頁的組,在遍歷組的項(xiàng),再遍歷項(xiàng)的控件調(diào)用保存。
偽代碼如下:
for(Page page:pages){? ? if(page.controls()!=null){? ? ? ? for(Group control:page.controls()){? ? ? ? ? ? //....? ? ? ? }? ? }}
好幾層迭代,每層迭代中還需要判斷是否包含相關(guān)的組
后來,又出現(xiàn)了一個(gè)新的需求,因?yàn)橐推渌绦蚵?lián)動(dòng),因此如果用戶修改了某個(gè)配置,那么應(yīng)該不等用戶點(diǎn)擊保存,就應(yīng)該寫到配置文件中,有點(diǎn)類似于VUE的雙向綁定。
這個(gè)時(shí)候面對這臃腫的代碼,簡直崩潰。
仔細(xì)梳理上面的需求可以發(fā)現(xiàn),由于設(shè)置程序分為好幾個(gè)頁,同時(shí)每個(gè)頁分為幾個(gè)組,每個(gè)組又有不同項(xiàng),每個(gè)項(xiàng)包含不同的控件。可以類比到每個(gè)國家的政治區(qū)域劃分,就好像每個(gè)省包含很多市,每個(gè)市又有不同的縣。而有些地方又有直轄市。因此,我們可以學(xué)習(xí)這種管理方式,讓每個(gè)分支自己管理自己的分組,而作為根節(jié)點(diǎn),我們只用管理根節(jié)點(diǎn)所接觸到的結(jié)構(gòu)即可。
成功優(yōu)化后的代碼,將會(huì)是如下效果:
for(Control page:pages){? ? page.save();}
簡直不能簡單的更多。
以上思想,便是組合模式的雛形。
組合模式將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)。組合模式使得用戶可以使用一致的方法操作單個(gè)對象和組合對象。
比如上面的代碼,我們可以首先定義一個(gè)接口:
public interface Control {? ? //添加節(jié)點(diǎn)? ? void add(Control control);? ? //刪除節(jié)點(diǎn)? ? void remove(Control control);? ? //設(shè)置層級,方便打印? ? void setLevel(int level);? ? //保存配置? ? void save();}
然后,依次定義Page , Group , Item
//定義page,group,item等相同public class Page implements Control {? ? private List<Control> children = new ArrayList<>();? ? private int level;? ? @Override? ? public void save() {? ? ? ? System.out.println("- Page");? ? ? ? for (Control control : children) {? ? ? ? ? ? control.save();? ? ? ? }? ? }? ? @Override? ? public void add(Control control) {? ? ? ? control.setLevel(1);? ? ? ? children.add(control);? ? }? ? @Override? ? public void remove(Control control) {? ? ? ? children.remove(control);? ? }? ? @Override? ? public void setLevel(int level) {? ? ? ? this.level=level;? ? }}
//其他相同
接下來,可以定義具體的節(jié)點(diǎn),這樣的節(jié)點(diǎn)在組合模式中稱為”葉子節(jié)點(diǎn)“
public class Button implements? Control {? ? private int level;? ? @Override? ? public void save() {? ? ? ? StringBuilder stringBuilder=new StringBuilder();? ? ? ? for (int i = 0; i < level; i++) {? ? ? ? ? ? stringBuilder.append('-').append(" ");? ? ? ? }? ? ? ? System.out.println(stringBuilder+" Button保存成功");? ? }? ? @Override? ? public void add(Control control) {? ? ? ? System.out.println("錯(cuò)誤的操作,不支持add()");? ? }? ? @Override? ? public void remove(Control control) {? ? ? ? System.out.println("錯(cuò)誤的操作,不支持remove()");? ? }? ? @Override? ? public void setLevel(int level) {? ? ? ? this.level=level;? ? }}
public class TextBox implements Control {? ? private int? level;? ? @Override? ? public void save() {? ? ? ? StringBuilder stringBuilder=new StringBuilder();? ? ? ? for (int i = 0; i < level; i++) {? ? ? ? ? ? stringBuilder.append('-').append(" ");? ? ? ? }? ? ? ? System.out.println(stringBuilder+"TextBox保存成功");? ? }? ? @Override? ? public void add(Control control) {? ? ? ? System.out.println("錯(cuò)誤的操作,不支持add()");? ? }? ? @Override? ? public void remove(Control control) {? ? ? ? System.out.println("錯(cuò)誤的操作,不支持remove()");? ? }? ? @Override? ? public void setLevel(int level) {? ? ? ? this.level=level;? ? }}
到這里,基本就執(zhí)行完了,可以看到實(shí)現(xiàn)Control接口的基本分為兩類,分別為葉子類和樹枝類,葉子節(jié)點(diǎn)作為最終的端點(diǎn),完成最后的操作,而樹枝類負(fù)責(zé)管理本身的所有葉子節(jié)點(diǎn)。
使用方式如下:
public static void main(String[] args) {? ? Page page=new Page();? ? Group group=new Group();? ? Item item =new Item();? ? Button button=new Button();? ? TextBox textBox=new TextBox();? ? page.add(group);? ? group.add(button);? ? group.add(item);? ? item.add(textBox);? ? page.save();}
然后我們就能看到打印出來的信息:
- Page- - Group- -? Button保存成功- - - Item- - - TextBox保存成功
可以看到,使用非常方便,并且更加便于維護(hù)。
定義:
組合模式: 組合模式(Composite Pattern)將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)。組合模式使得用戶可以使用一致的方法操作單個(gè)對象和組合對象。
組合模式屬于結(jié)構(gòu)型模式
UML
組合模式如上所示,由于樹枝和葉子有著不同的操作,而葉子節(jié)點(diǎn)一般不存在添加子節(jié)點(diǎn)和刪除子節(jié)點(diǎn)操作,所有有時(shí)候?yàn)榱吮苊庹{(diào)用到葉子節(jié)點(diǎn)的這個(gè)非法操作。也可以單獨(dú)實(shí)現(xiàn)葉子節(jié)點(diǎn),這樣的模式被稱為安全模式。
應(yīng)用場景
一般應(yīng)用在哪些具有層級嵌套的結(jié)構(gòu)中,比如組織機(jī)構(gòu)樹的遍歷,比如各種控件子控件的管理等等。使用組合模式可以屏蔽用戶對層級結(jié)構(gòu)的感知。
比如MyBatis對動(dòng)態(tài)SQL的解析
比如C#中的各個(gè)控件都繼承自Controls
優(yōu)點(diǎn)
客戶端調(diào)用簡單,客戶端只需管理好根節(jié)點(diǎn)即可,不必判斷組件類型,也不用為不同的組件添加if-else
方便維護(hù),使用組合模式可以非常方便的添加擴(kuò)展層級,并且不用修改原有的代碼,符合開閉原則
缺點(diǎn)
由于屏蔽了樹枝類和葉子類的區(qū)別,因此如果某些代碼不小心調(diào)用了葉子類的非法方法,那只能在運(yùn)行時(shí)才能檢測出來。
總結(jié)
當(dāng)代碼中存在一些層級的邏輯結(jié)構(gòu),就非常的適合使用的組合模式來進(jìn)行設(shè)計(jì),這樣的設(shè)計(jì)能使得代碼非常簡單,同時(shí)組合模式又分為透明模式和安全模式,應(yīng)該按照實(shí)際情況選擇使用。