Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> java/android 設計模式學習筆記(12)---組合模式

java/android 設計模式學習筆記(12)---組合模式

編輯:關於Android編程

這篇我們來介紹一下組合模式(Composite Pattern),它也稱為部分整體模式(Part-Whole Pattern),結構型模式之一。組合模式比較簡單,它將一組相似的對象看作一個對象處理,並根據一個樹狀結構來組合對象,然後提供一個統一的方法去訪問相應的對象,以此忽略掉對象與對象集合之間的差別。這個最典型的例子就是數據結構中的樹了,如果一個節點有子節點,那麼它就是枝干節點,如果沒有子節點,那麼它就是葉子節點,那麼怎麼把枝干節點和葉子節點統一當作一種對象處理呢?這就需要用到組合模式了。
  這裡寫圖片描述

特點

組合模式允許你將對象組合成樹形結構來表現“整體/部分”層次結構,並且能夠讓客戶端以一致的方式處理個別對象以及組合對象。
  組合模式讓我們能用樹形方式創建對象的結構,樹裡面包含了組合構件以及葉子構件的對象,而且能夠把相同的操作應用在組合構件和葉子構件上,換句話說,在大多數情況下我們可以忽略組合對象和葉子對象之間的差別。組合模式使用的場景:

表示對象的部分-整體結構層次時;從一個整體中能夠獨立出部分模塊或功能的場景。

 

UML類圖

組合模式在實際使用中會有兩種情況:安全的組合模式與透明的組合模式。

安全的組合模式

我們先來看看安全組合模式的 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

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved