編輯:關於Android編程
這篇我們來介紹一下組合模式(Composite Pattern),它也稱為部分整體模式(Part-Whole Pattern),結構型模式之一。組合模式比較簡單,它將一組相似的對象看作一個對象處理,並根據一個樹狀結構來組合對象,然後提供一個統一的方法去訪問相應的對象,以此忽略掉對象與對象集合之間的差別。這個最典型的例子就是數據結構中的樹了,如果一個節點有子節點,那麼它就是枝干節點,如果沒有子節點,那麼它就是葉子節點,那麼怎麼把枝干節點和葉子節點統一當作一種對象處理呢?這就需要用到組合模式了。
組合模式允許你將對象組合成樹形結構來表現“整體/部分”層次結構,並且能夠讓客戶端以一致的方式處理個別對象以及組合對象。
組合模式讓我們能用樹形方式創建對象的結構,樹裡面包含了組合構件以及葉子構件的對象,而且能夠把相同的操作應用在組合構件和葉子構件上,換句話說,在大多數情況下我們可以忽略組合對象和葉子對象之間的差別。組合模式使用的場景:
組合模式在實際使用中會有兩種情況:安全的組合模式與透明的組合模式。
我們先來看看安全組合模式的 uml 類圖:
vcfJq6O6PC9wPg0KQ29tcG9uZW50o7qz6c/zuPm92rXjo6zOqtfpus/W0LXEttTP88n5w/e907/a0NDOqqOsysfL+dPQvdq147XEs+nP86Gj1NrKyrWxtcTH6b/2z8KjrMq1z9bL+dPQwOC5stPQvdO/2rXEyLHKodDQzqqho8n5w/fSu7j2vdO/2tPD09q3w87Kus253MDtIENvbXBvbmVudCC1xNfTvdq146Gjv8nU2rXduem94bm51tC2qNLl0ru49r3Tv9qjrNPD09q3w87K0ru49ri4vdq146OssqLU2rrPysq1xMfpv/bPwsq1z9bL/KO7Q29tcG9zaXRlo7rU9rzTtqjS5damuMm92rXjtcTQ0M6qo6y05rSi19O92rXjo6zKtc/WIENvbXBvbmVudCC907/a1tC1xNPQudi1xLLZ1/eju0xlYWajutTa1+m6z9bQse3KvtK219O94bXjttTP86Os0rbX073atePDu9PQ19O92rXjo6zKtc/WIENvbXBvbmVudCC907/a1tC1xMirsr+y2df3o7tDbGllbnSjus2ouf0gQ29tcG9uZW50o6xDb21wb3NpdGUgus0gTGVhZiDA4LLZ193X6brPvdq147bUz/Oho6GhoaG+3bTLztLDx7/J0tTQtLP2sLLIq9fpus/Eo8q9tcTNqNPDtPrC66O6PGJyIC8+DQo8c3Ryb25nPkNvbXBvbmVudC5jbGFzczwvc3Ryb25nPg0KPHA+Jm5ic3A7PC9wPg0KPHByZSBjbGFzcz0="brush:java;">
public abstract class Component {
public abstract void operation();
}
Composite.class
public class Composite extends Component{
private ArrayList componentList = new ArrayList<>();
@Override
public void operation() {
Log.e("shawn", "this is composite " + this + " -------start");
for (Component component : componentList) {
component.operation();
}
Log.e("shawn", "this is composite " + this + " -------end");
}
public void add(Component child) {
componentList.add(child);
}
public void remove(Component child) {
componentList.remove(child);
}
public Component getChild(int position) {
return componentList.get(position);
}
}
Leaf.class
public class Leaf extends Component{
@Override
public void operation() {
Log.e("shawn", "this if leaf " + this);
}
}
Client 測試代碼:
Composite root = new Composite();
Leaf leaf1 = new Leaf();
Composite branch = new Composite();
root.add(leaf1);
root.add(branch);
Leaf leaf2 = new Leaf();
branch.add(leaf2);
root.operation();
break;
最後輸出結果:
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------start
com.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@1d7d4031
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------start
com.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@5dae497
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------end
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------end
代碼很簡單,結果就是一個簡單的樹形結構,但是仔細看看客戶端代碼,就能發現它違反了 6 個設計模式原則中依賴倒置原則,客戶端不應該直接依賴於具體實現,而應該依賴於抽象,既然是面向接口編程,就應該把更多的焦點放在接口的設計上,於是這樣就產生了透明的組合模式。
來看看透明的組合模式 uml 類圖:
和安全的組合模式差異就是在將 Composite 的操作放到了 Component 中,這就造成 Leaf 角色也要實現 Component 中的所有方法。實現的代碼做出相應改變:
Component.class
public interface Component {
void operation();
void add(Component child);
void remove(Component child);
Component getChild(int position);
}
Composite.class
public class Composite implements Component{
private ArrayList componentList = new ArrayList<>();
@Override
public void operation() {
Log.e("shawn", "this is composite " + this + " -------start");
for (Component component : componentList) {
component.operation();
}
Log.e("shawn", "this is composite " + this + " -------end");
}
@Override
public void add(Component child) {
componentList.add(child);
}
@Override
public void remove(Component child) {
componentList.remove(child);
}
@Override
public Component getChild(int position) {
return componentList.get(position);
}
}
Leaf.class
public class Leaf implements Component {
@Override
public void operation() {
Log.e("shawn", "this if leaf " + this);
}
@Override
public void add(Component child) {
throw new UnsupportedOperationException("leaf can't add child");
}
@Override
public void remove(Component child) {
throw new UnsupportedOperationException("leaf can't remove child");
}
@Override
public Component getChild(int position) {
throw new UnsupportedOperationException("leaf doesn't have any child");
}
}
Client 測試代碼
Component root = new Composite();
Component leaf1 = new Leaf();
Component branch = new Composite();
root.add(leaf1);
root.add(branch);
Component leaf2 = new Leaf();
branch.add(leaf2);
root.operation();
最後產生的結果是一樣的,由於是在 Component 類中定義了所有的行為,所以客戶端就不用直接依賴於具體 Composite 和 Leaf 類的實現,遵循了依賴倒置原則——依賴抽象,而不依賴具體實現。但是也違反了單一職責原則與接口隔離原則,讓 Leaf 類繼承了它本不應該有的方法,並且不太優雅的拋出了 UnsupportedOperationException ,這樣做的目的就是為了客戶端可以透明的去調用對應組件的方法,將枝干節點和子節點一視同仁。
另外,將 Component 寫成一個虛基類,並且實現所有的 Composite 方法,而且默認都拋出異常,只讓 Composite 去覆蓋重寫父類的方法,而 Leaf 類就不需要去實現 Composite 的相關方法,這麼去實現當然也是可以的。
安全的組合模式將責任區分開來放在不同的接口中,這樣一來,設計上就比較安全,也遵循了單一職責原則和接口隔離原則,但是也讓客戶端必須依賴於具體的實現;透明的組合模式,以單一職責原則和接口隔離原則原則換取透明性,遵循依賴倒置原則,客戶端就直接依賴於 Component 抽象即可,將 Composite 和 Leaf 一視同仁,也就是說,一個元素究竟是枝干節點還是葉子節點,對客戶端是透明的。
所以這是一個很典型的折衷案例,盡管我們受到設計原則的指導,但是我們總是需要觀察某原則對我們的設計所造成的影響。有時候這個需要去根據實際案例去分析,畢竟有些時候 6 種設計模式原則在實際使用過程中是會沖突的,是讓客戶端每次使用的時候都去先檢查類型還是賦予子節點不應該有的行為,這都取決於設計者的觀點,總體而言,這兩種方案都是可行的。
組合模式在實際生活過程中的例子就數不勝數了,比如菜單、文件夾等等。我們這就以 Android 中非常經典的實現為例來分析一下。View 和 ViewGroup 想必應該都非常熟悉,其實他們用到的就是組合模式,我們先來看看他們之間的 uml 類圖:
ViewManager 這個類在java/android 設計模式學習筆記(8)—橋接模式中提到過,WindowManager 也繼承了該類:
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
*
Throws {@link android.view.WindowManager.BadTokenException} for certain programming * errors, such as adding a second view to a window without removing the first view. *
Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a * secondary {@link Display} and the specified display can't be found * (see {@link android.app.Presentation}). * @param view The view to be added to this window. * @param params The LayoutParams to assign to view. */ public void addView(View view, ViewGroup.LayoutParams params); public void updateViewLayout(View view, ViewGroup.LayoutParams params); public void removeView(View view); }
只定義了關於 View 操作的三個方法。ViewParent 類是用來定義一個 父 View 角色所具有的職責,在 Android 中,一般能成為父 View 的也只有 ViewGroup:
/**
* Defines the responsibilities for a class that will be a parent of a View.
* This is the API that a view sees when it wants to interact with its parent.
*
*/
public interface ViewParent {
/**
* Called when something has changed which has invalidated the layout of a
* child of this view parent. This will schedule a layout pass of the view
* tree.
*/
public void requestLayout();
/**
* Indicates whether layout was requested on this view parent.
*
* @return true if layout was requested, false otherwise
*/
public boolean isLayoutRequested();
....
}
從 uml 類圖中可以注意到一點,ViewGroup 和 View 使用的安全的組合模式,而不是透明的組合模式,怪不得有時候使用前需要將 View 強轉成 ViewGroup 。
使用組合模式,我們能把相同的操作應用在組合和個別對象上,換句話說,在大多數情況下,我們可以忽略對象組合和個別對象之間的差別。組合模式適用於一些界面 UI 的結構設計上,典型的例子就是Android,iOS 和 Java 等都提供了相應的 UI 框架。
組合模式的優點:
https://github.com/zhaozepeng/Design-Patterns/tree/master/CompositePattern
Chromium的Extension Page其實就是網頁,因此它們的加載過程與普通網頁相同。常見的Extension Page有Background Page和Popu
開始本專欄的第一個簡易案例: 首先設置兩個布局文件,一個布局文件進行輸入數據,獲取加法運算;另一個布局文件進行顯示最終結果。Activity1啟動Activit
一:在Android程序開發中,我們經常會去用到Shape這個東西去定義各種各樣的形狀,首先我們了解一下Shape下面有哪些標簽,都代表什麼意思:(1).solid:填充
線程池簡單點就是任務隊列+線程組成的。接下來我們來簡單的了解下ThreadPoolExecutor的源碼。先看ThreadPoolExecutor的簡單類圖,對Threa