Android 組合模式(View與ViewGroup)
Android 設計模式系列文章Android 23種設計模式
前言
組合設計模式,又被稱為部分整體模式。組合模式就是把一組比較相似的物件當做一樣的物件處理。並根據樹狀結構來組合物件,然後提供可以一個統一方法去訪問這些物件,這樣就可以忽略物件和集合之間的差別。


我們可以看下這兩張樹狀圖。公司架構圖裡邊,樹狀圖的各個節點node,還有各個葉子leaf實際上他們是由差別的。而組合模式就是把node當做一樣的物件處理。leaf也當做一樣的物件處理。而當我們要對node和leaf操作的時候,不需要考慮他是節點還是葉子,組合模式提供一致的方式來操作。這就是組合模式了。
組合模式定義
將部分整體的層次結構轉換為樹狀結構,是的客戶訪問物件和組合物件具有一致性。
組合模式舉例
組合模式在寫法上分為透明組合模式和安全組合模式。我們先來透明組合模式
透明組合模式
1、先抽象出方法
public abstract class Component { String name; public Component(String name){ this.name = name; } public abstract void print(); public abstract void addChild(Component component); public abstract void removeChild(Component component); public abstract Component getChild(int index); }
2、 node節點
由於root根節點根node幾乎一樣,這裡就直接定義node未單獨定義一個root節點
public class Node extends Component { private static final String TAG = Node.class.getSimpleName(); private List<Component> list = new ArrayList<>(); public Node(String name) { super(name); } @Override public void print() { Log.d(TAG,name); for (Component component:list) { component.print(); } } @Override public void addChild(Component component) { list.add(component); } @Override public void removeChild(Component component) { list.remove(component); } @Override public Component getChild(int index) { return list.get(index); } }
3、 葉子
枝幹很簡單就是實現我們的增刪查和遍歷。然後看葉子
public class Leaf extends Component { private static final String TAG = Leaf.class.getSimpleName(); public Leaf(String name) { super(name); } @Override public void print() { Log.d(TAG,name); } @Override public void addChild(Component component) { Log.d(TAG,"葉子節點,沒有子節點"); } @Override public void removeChild(Component component) { Log.d(TAG,"葉子節點,沒有子節點"); } @Override public Component getChild(int index) { Log.d(TAG,"葉子節點,沒有子節點"); return null; } }
4、 呼叫
由於葉子節點沒有子節點了,所以增刪查詢就沒有作用了。接下來呼叫
Component root = new Node("XX公司"); Component software = new Node("軟體部"); Component hardware = new Node("硬體部"); Component androidSoftware = new Leaf("android"); Component iosSoftware = new Leaf("ios"); Component layout = new Leaf("layout"); root.addChild(software); root.addChild(hardware); software.addChild(androidSoftware); software.addChild(iosSoftware); hardware.addChild(layout); root.print();
5、 列印
上也說了由於node和root極其相似,所以就複用了。看輸出
01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Node: XX公司 01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Node: 軟體部 01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Leaf: android 01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Leaf: ios 01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Node: 硬體部 01-24 11:17:52.649 30713-30713/com.yink.designpattern.designpattern D/Leaf: layout
到此,我想你已經理解什麼是組合模式了。這時候有的讀者可能發現了,葉子節點很多方法沒必要存在,如果方法多的時候,程式碼就十分繁瑣多於。這個時候另外一種組合模式的寫法就出來了。
安全組合模式
1、抽象方法
安全組合模式和上面的透明組合模式最大差別呢就是把抽象方法簡化了,只留了我們都需要的。
public abstract class SafeComponent { protected String name; public SafeComponent(String name) { this.name = name; } public abstract void print(); }
2、node
增刪查不再是統一介面
public class SafeNode extends SafeComponent { private static final String TAG = SafeNode.class.getSimpleName(); private List<SafeComponent> list = new ArrayList<>(); public SafeNode(String name) { super(name); } @Override public void print() { Log.d(TAG,name); for (SafeComponent safeComponent:list) { safeComponent.print(); } } public void addChild(SafeComponent safeComponent) { list.add(safeComponent); } public void removeChild(SafeComponent safeComponent) { list.remove(safeComponent); } public SafeComponent getChild(int index) { return list.get(index); } }
3、leaf
public class SafeLeaf extends SafeComponent { private static final String TAG = SafeLeaf.class.getSimpleName(); public SafeLeaf(String name) { super(name); } @Override public void print() { Log.d(TAG,name); } }
4、呼叫
SafeComponent root = new SafeNode("XX公司"); SafeComponent software = new SafeNode("軟體部"); SafeComponent hardware = new SafeNode("硬體部"); SafeComponent androidSoftware = new SafeLeaf("android"); SafeComponent iosSoftware = new SafeLeaf("ios"); SafeComponent layout = new SafeLeaf("layout"); ((SafeNode) root).addChild(software); ((SafeNode) root).addChild(hardware); ((SafeNode) software).addChild(androidSoftware); ((SafeNode) software).addChild(iosSoftware); ((SafeNode) hardware).addChild(layout); root.print();
呼叫過後輸出和上面是一樣的,這裡就不重複了。安全組合模式分工就很明確了。它還有一個好處就是當我們add/remove/getchild的時候,我們能知道具體的類是什麼了,而透明組合模式就得在執行時去判斷,比較麻煩。
View和ViewGroup
1、View和ViewGroup就是安全組合容器
組合模式Android中最經典的用法無非就是View和ViewGroup的運用了。我們都知道View還有Textview、Button他們都是繼承於View。他們就像葉子。而ViewGroup是容器,ViewGroup可以新增View,而View不能新增ViewGroup。這不就是安全組合模式裡邊Leaf和Node的關係嗎。所以它就是安全組合模式的一種運用。
2、ViewGroup容器實現方式
2.1、介面新增操作子檢視方法
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
首先ViewGroup繼承自View,所有ViewGroup擁有view的公開方法。具有view的特性。然後ViewGroup繼承了兩個介面。我們先看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); }
一眼就明白了,跟我們之前的node一樣,添加了add/remove等對子檢視的操作方法。
在看ViewParent
public interface ViewParent { public void requestLayout(); public void invalidateChild(View child, Rect r); public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset); public void requestChildFocus(View child, View focused); ... }
這裡省略了ViewParent其它介面,我們會看到我們很多熟悉的方法,請求重新佈局、獲取子檢視焦點等等。這裡也是一些對子檢視的操作。
2.2、繼承View複寫onalyout操作子元素
ViewGroup是一個抽象類,通篇ViewGroup的程式碼,ViewGroup就一個為了把View的onlayout方法重置為抽象方法。為啥View要這麼做呢?我們知道View的onlatyou的方法是,View在父檢視中layout佈局過後呼叫onlayout方法,onlayout只是一個空實現,因為View已經佈局完成。onlayout沒有其他的實現意義。而ViewGroup作為一個檢視容器,ViewGroup呼叫layout佈局完自己後。還需要佈局子檢視。所以它把onlayout重置為抽象方法, 讓子類必須實現。
2.3、其他方法
除了onlayout還有一些其他方法。我們再舉一個例子。
View中的dispatchDraw是一個空實現,而ViewGroup中就是為了遍歷然後drawChild。省略部分程式碼
protected void dispatchDraw(Canvas canvas) { ... for (int i = 0; i < childrenCount; i++) { while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) { final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { transientIndex = -1; } } final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } .... }
總結
現在我想大家對什麼是組合模式已經有桿秤了。最後我想說下組合模式的優缺點
優點:
1、組合模式可以以層次結構清楚的定義複雜物件。讓高層次忽略層次差異。
2、高層次可以使用統一的方法而不用擔心它是枝幹還是葉子。比如ViewGroup的dispatchView
3、組合模式可以形成複雜的樹形結構,但對樹形機構的控制卻非常簡單。
缺點:
1、葉子型別不能控制。比如我想控制ViewGroup新增的View必須為TextView的時候,約束起來就很麻煩。特別是型別多的時候。