前言
Android的設計模式系列文章介紹,歡迎關注,持續(xù)更新中:
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創(chuàng)建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態(tài)模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式
1.定義
將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。
2.介紹
- 組合模式屬于結構型模式。
- 組合模式有時叫做部分—整體模式,主要是描述部分與整體的關系。
- 組合模式實際上就是個樹形結構,一棵樹的節(jié)點如果沒有分支,就是葉子節(jié)點;如果存在分支,則是樹枝節(jié)點。
-
我們平時遇到的最典型的組合結構就是文件和文件夾了,具體的文件就是葉子節(jié)點,而文件夾下還可以存在文件和文件夾,所以文件夾一般是樹枝節(jié)點。
文件夾樹形結構.png
3.UML類圖
角色說明:
- Component(抽象組件角色):定義參加組合對象的共有方法和屬性,可以定義一些默認的函數(shù)或屬性。
- Leaf(葉子節(jié)點):葉子沒有子節(jié)點,因此是組合樹的最小構建單元。
- Composite(樹枝節(jié)點):定義所有枝干節(jié)點的行為,存儲子節(jié)點,實現(xiàn)相關操作。
4.實現(xiàn)(透明的組合模式)
下面以網(wǎng)站頁面為例子,一個頁面有多個欄目以及內(nèi)容,欄目又可以包含子欄目以及具體內(nèi)容,這就是一個樹形結構。如下圖:
4.1 創(chuàng)建抽象組件角色
這里就是一個網(wǎng)站的抽象頁面元素:
public abstract class PageElement {//頁面
protected List<PageElement> mPageElements = new ArrayList<>();//用來保存頁面元素
private String name;
public PageElement(String name) {
this.name = name;
}
public abstract void addPageElement(PageElement pageElement);//添加欄目或者具體內(nèi)容
public abstract void rmPageElement(PageElement pageElement);//刪除欄目或者具體內(nèi)容
public abstract void clear();//清空所有元素
public abstract void print(String placeholder);//打印頁面結構
public String getName() {
return name;
}
}
4.2 創(chuàng)建葉子節(jié)點
葉子節(jié)點繼承了抽象組件角色,但是由于沒有分支,所以一些添加刪除操作是實現(xiàn)不了的。葉子節(jié)點都是一些具體的內(nèi)容,比如具體的音樂內(nèi)容、視屏內(nèi)容等等。
public class Content extends PageElement {//具體內(nèi)容
public Content(String name) {
super(name);
}
@Override
public void addPageElement(PageElement pageElement) {
throw new UnsupportedOperationException("不支持此操作");
}
@Override
public void rmPageElement(PageElement pageElement) {
throw new UnsupportedOperationException("不支持此操作");
}
@Override
public void clear() {
throw new UnsupportedOperationException("不支持此操作");
}
@Override
public void print(String placeholder) {
System.out.println(placeholder + "──" + getName());
}
}
4.3 創(chuàng)建樹枝節(jié)點
樹枝節(jié)點能夠刪除添加葉子或樹枝。
public class Column extends PageElement {//欄目
public Column(String name) {
super(name);
}
@Override
public void addPageElement(PageElement pageElement) {
mPageElements.add(pageElement);
}
@Override
public void rmPageElement(PageElement pageElement) {
mPageElements.remove(pageElement);
}
@Override
public void clear() {
mPageElements.clear();
}
/**
* @param placeholder 占位符
*/
@Override
public void print(String placeholder) {
//利用遞歸來打印文件夾結構
System.out.println(placeholder + "└──" + getName());
Iterator<PageElement> i = mPageElements.iterator();
while (i.hasNext()) {
PageElement pageElement = i.next();
pageElement.print(placeholder + " ");
}
}
}
4.4 客戶端測試:
public void test() {
//創(chuàng)建網(wǎng)站根頁面 root
PageElement root = new Column("網(wǎng)站頁面");
//網(wǎng)站頁面添加兩個欄目:音樂,視屏;以及一個廣告內(nèi)容。
PageElement music = new Column("音樂");
PageElement video = new Column("視屏");
PageElement ad = new Content("廣告");
root.addPageElement(music);
root.addPageElement(video);
root.addPageElement(ad);
//音樂欄目添加兩個子欄目:國語,粵語
PageElement chineseMusic = new Column("國語");
PageElement cantoneseMusic = new Column("粵語");
music.addPageElement(chineseMusic);
music.addPageElement(cantoneseMusic);
//國語,粵語欄目添加具體內(nèi)容
chineseMusic.addPageElement(new Content("十年.mp3"));
cantoneseMusic.addPageElement(new Content("明年今日.mp3"));
//視頻欄目添加具體內(nèi)容
video.addPageElement(new Content("唐伯虎點秋香.avi"));
//打印整個頁面的內(nèi)容
root.print("");
}
輸出結果:
└──網(wǎng)站頁面
└──音樂
└──國語
──十年.mp3
└──粵語
──明年今日.mp3
└──視屏
──唐伯虎點秋香.avi
──廣告
4.5 其他說明:
上面的例子可以看到葉子節(jié)點其實并不需要添加刪除等方法,但由于葉子節(jié)點實際上是依賴了抽象組件角色。一方面,這遵循了依賴倒置原則——依賴抽象,而不依賴具體實現(xiàn);同時,也保證了葉子節(jié)點跟樹枝節(jié)點具體相同的結構,即他們具有同樣的方法接口,能夠讓客戶端以一致的方式去處理單個對象和組合對象。但另一方,這違反了單一職責原則與接口隔離原則,讓 葉子節(jié)點繼承了它本不應該有的方法,并且不太優(yōu)雅的拋出了 UnsupportedOperationException
。這實際叫透明的組合模式。
5. 安全的組合模式
另外一種組合模式叫安全的組合模式。這種模式客戶端在使用的時候必須依賴具體的實現(xiàn),這違反了依賴倒置原則,但遵循了單一職責原則與接口隔離原則。
5.1 實現(xiàn)
public abstract class PageElement {//頁面
private String name;
public PageElement(String name) {
this.name = name;
}
//抽象組件角色去掉增刪等接口
public abstract void print(String placeholder);
public String getName() {
return name;
}
}
public class Content extends PageElement {//具體內(nèi)容,只專注自己的職責
public Content(String name) {
super(name);
}
@Override
public void print(String placeholder) {
System.out.println(placeholder + "──" + getName());
}
}
public class Column extends PageElement {//欄目
private List<PageElement> mPageElements = new ArrayList<>();//用來保存頁面元素
public Column(String name) {
super(name);
}
public void addPageElement(PageElement pageElement) {
mPageElements.add(pageElement);
}
public void rmPageElement(PageElement pageElement) {
mPageElements.remove(pageElement);
}
public void clear() {
mPageElements.clear();
}
@Override
public void print(String placeholder) {
System.out.println(placeholder + "└──" + getName());
Iterator<PageElement> i = mPageElements.iterator();
while (i.hasNext()) {
PageElement pageElement = i.next();
pageElement.print(placeholder + " ");
}
}
}
public void test() {//客戶端測試方法
//依賴具體的實現(xiàn)類Column
Column root = new Column("網(wǎng)站頁面");
Column music = new Column("音樂");
Column video = new Column("視屏");
PageElement ad = new Content("廣告");
root.addPageElement(music);
root.addPageElement(video);
root.addPageElement(ad);
Column chineseMusic = new Column("國語");
Column cantoneseMusic = new Column("粵語");
music.addPageElement(chineseMusic);
music.addPageElement(cantoneseMusic);
chineseMusic.addPageElement(new Content("十年.mp3"));
cantoneseMusic.addPageElement(new Content("明年今日.mp3"));
video.addPageElement(new Content("唐伯虎點秋香.avi"));
root.print("");
}
5.2 對比
安全的組合模式將職責區(qū)分開來放在不同的接口中,這樣一來,設計上就比較安全,也遵循了單一職責原則和接口隔離原則,但是也讓客戶端必須依賴于具體的實現(xiàn);透明的組合模式,以違反單一職責原則和接口隔離原則來換取透明性,但遵循依賴倒置原則,客戶端可以直接依賴于抽象組件即可,將葉子和樹枝一視同仁,也就是說,一個元素究竟是枝干節(jié)點還是葉子節(jié)點,對客戶端是透明的。
一方面,我們寫代碼時應該遵循各種設計原則,但實際上,有些設計模式原則在使用時會發(fā)生沖突,這就需要我們根據(jù)實際情況去衡量做出取舍,適合自己的才是最好的。
6. 應用場景
- 表示對象的部分-整體層次結構時。
- 從一個整體中能夠獨立出部分模塊或功能時。
7. 優(yōu)點
- 高層模塊(客戶端)調(diào)用簡單。局部和整體都是同樣的結構,客戶端無需關心是局部還是整體。
- 新增節(jié)點容易。無需對現(xiàn)有類庫進行任何修改,符合開閉原則。
8. 缺點
- 不同的組合模式實現(xiàn)有不同的缺點,具體看上面的分析。
9. Android中的源碼分析
Android源碼中,ViewGroup
和 View
就是典型的組合模式。
9.1 View類
View相當與葉子節(jié)點,里面沒有添加刪除View等操作。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
//具體代碼略
}
9.2 ViewGroup類
ViewGroup
實際上是View的子類,同時ViewGroup
中實現(xiàn)了添加刪除View等操作,因此可以作為容器存放view。
public abstract class ViewGroup extends android.view.View implements ViewParent, ViewManager {//繼承View
@Override
public void addView(View child, android.view.ViewGroup.LayoutParams params) {//添加view
//具體實現(xiàn)代碼略
}
@Override
public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {//更新view
//具體實現(xiàn)代碼略
}
@Override
public void removeView(View view) {//移除view
//具體實現(xiàn)代碼略
}
//其他代碼略
}
9.3 ViewManager接口
實際上ViewGroup
中的了添加刪除View是實現(xiàn)了ViewManager
接口中的方法:
public interface ViewManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
9.4 其他
可以看出ViewGroup 和 View 使用的是安全的組合模式,而不是透明的組合模式。
相關文章閱讀
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創(chuàng)建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態(tài)模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式