zoukankan      html  css  js  c++  java
  • 设计模式11---组合模式(Composite Pattern)

    一、组合模式定义

    将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

         

     

        如上图所示(截取自《Head First Design Patterns》一书),主要包括三个部分: 

        1. Component抽象组件。定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。 

        2. Leaf叶子节点。构成组合树的最小构建单元。 

        3. Composite树枝节点组件。它的作用是组合树枝节点和叶子节点形成一个树形结构。 

    Component : 组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 的子部件。

     1 abstract class Component {
     2     protected String name;
     3 
     4     public Component(String name) {
     5         this.name = name;
     6     }
     7 
     8     public abstract void Add(Component c);
     9     public abstract void Remove(Component c);
    10     public abstract void Display(int depth);
    11 }

    Leaf : 表示叶节点对象。叶子节点没有子节点。

     1 class Leaf extends Component {
     2 
     3     public Leaf(String name) {
     4         super(name);
     5     }
     6 
     7     @Override
     8     public void Add(Component c) {
     9         System.out.println("Can not add to a leaf");
    10     }
    11 
    12     @Override
    13     public void Remove(Component c) {
    14         System.out.println("Can not remove from a leaf");
    15     }
    16 
    17     @Override
    18     public void Display(int depth) {
    19         String temp = "";
    20         for (int i = 0; i < depth; i++) 
    21             temp += '-';
    22         System.out.println(temp + name);
    23     }
    24 
    25 }

    Composite : 定义枝节点行为,用来存储子部件,在 Component 接口中实现与子部件相关的操作。例如 Add 和 Remove。

     1 class Composite extends Component {
     2 
     3     private List<Component> children = new ArrayList<Component>();
     4 
     5     public Composite(String name) {
     6         super(name);
     7     }
     8 
     9     @Override
    10     public void Add(Component c) {
    11         children.add(c);
    12     }
    13 
    14     @Override
    15     public void Remove(Component c) {
    16         children.remove(c);
    17     }
    18 
    19     @Override
    20     public void Display(int depth) {
    21         String temp = "";
    22         for (int i = 0; i < depth; i++) 
    23             temp += '-';
    24         System.out.println(temp + name);
    25 
    26         for (Component c : children) {
    27             c.Display(depth + 2);
    28         }
    29     }
    30 
    31 }

    Client : 通过 Component 接口操作结构中的对象。

     1 public class CompositePattern {
     2 
     3 public static void main(String[] args) {
     4     Composite root = new Composite("root");
     5     root.Add(new Leaf("Leaf A"));
     6     root.Add(new Leaf("Leaf B"));
     7 
     8     Composite compX = new Composite("Composite X");
     9     compX.Add(new Leaf("Leaf XA"));
    10     compX.Add(new Leaf("Leaf XB"));
    11     root.Add(compX);
    12 
    13     Composite compXY = new Composite("Composite XY");
    14     compXY.Add(new Leaf("Leaf XYA"));
    15     compXY.Add(new Leaf("Leaf XYB"));
    16     compX.Add(compXY);
    17 
    18     root.Display(1);
    19 }
    20 
    21 }

    二、组合模式优势 

      节点自由扩展增加。使用组合模式,如果想增加一个树枝节点或者叶子节点都是很简单的,只要找到它的父节点就可以了,非常容易扩展,符合“开闭原则”。应用最广的模式之一。应用在维护和展示部分-整体关系的场景,如树形菜单、文件夹管理等等。一棵树形结构的所有节点都是Component,局部和整体对调用者来说都是一样的,没有区别,所以高层模块不比关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

       1、可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。

          2、客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。

          3、定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。

          4、更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。

    组合模式的缺点: 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。

    使用场景:

        1、需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们。

          2、让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节。

     三、组合模式在Android源码中的应用 

        在Android源码中,都能找到使用组合模式的例子,其中在《Android源码学习之观察者模式应用》介绍到的ViewGroup和View的结构就是一个组合模式,结构图如下所示: 

         现在来看看它们是如何利用组合模式组织在一起的,首先在View类定义了有关具体操作,然后在ViewGroup类中继承View类,并添加相关的增加、删除和查找孩子View节点,代码如下: 

    /*
    * @attr ref android.R.styleable#ViewGroup_clipChildren * @attr ref android.R.styleable#ViewGroup_clipToPadding * @attr ref android.R.styleable#ViewGroup_layoutAnimation * @attr ref android.R.styleable#ViewGroup_animationCache * @attr ref android.R.styleable#ViewGroup_persistentDrawingCache * @attr ref android.R.styleable#ViewGroup_alwaysDrawnWithCache * @attr ref android.R.styleable#ViewGroup_addStatesFromChildren * @attr ref android.R.styleable#ViewGroup_descendantFocusability * @attr ref android.R.styleable#ViewGroup_animateLayoutChanges */ public abstract class ViewGroup extends View implements ViewParent, ViewManager {

     接着看增加孩子节点函数: 

      /**
         * Adds a child view. If no layout parameters are already set on the child, the
         * default parameters for this ViewGroup are set on the child.
         *
         * @param child the child view to add
         *
         * @see #generateDefaultLayoutParams()
         */
        public void addView(View child) {
            addView(child, -1);
        }
    
        /**
         * Adds a child view. If no layout parameters are already set on the child, the
         * default parameters for this ViewGroup are set on the child.
         *
         * @param child the child view to add
         * @param index the position at which to add the child
         *
         * @see #generateDefaultLayoutParams()
         */
        public void addView(View child, int index) {
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = generateDefaultLayoutParams();
                if (params == null) {
                    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
                }
            }
            addView(child, index, params);
        }
    
        /**
         * Adds a child view with this ViewGroup's default layout parameters and the
         * specified width and height.
         *
         * @param child the child view to add
         */
        public void addView(View child, int width, int height) {
            final LayoutParams params = generateDefaultLayoutParams();
            params.width = width;
            params.height = height;
            addView(child, -1, params);
        }
    
        /**
         * Adds a child view with the specified layout parameters.
         *
         * @param child the child view to add
         * @param params the layout parameters to set on the child
         */
        public void addView(View child, LayoutParams params) {
            addView(child, -1, params);
        }
    
        /**
         * Adds a child view with the specified layout parameters.
         *
         * @param child the child view to add
         * @param index the position at which to add the child
         * @param params the layout parameters to set on the child
         */
        public void addView(View child, int index, LayoutParams params) {
            if (DBG) {
                System.out.println(this + " addView");
            }
    
            // addViewInner() will call child.requestLayout() when setting the new LayoutParams
            // therefore, we call requestLayout() on ourselves before, so that the child's request
            // will be blocked at our level
            requestLayout();
            invalidate(true);
            addViewInner(child, index, params, false);
        } 

        在ViewGroup中我们找到了添加addView()方法,有了增加孩子节点,肯定有相对应删除孩子节点的方法,接着看:  

     1 @Override
     2 public void removeView(View view) {
     3      if (removeViewInternal(view)) {
     4          requestLayout();
     5          invalidate(true);
     6      }
     7  }
     8 private boolean removeViewInternal(View view) {
     9     final int index = indexOfChild(view);
    10     if (index >= 0) {
    11         removeViewInternal(index, view);
    12         return true;
    13     }
    14     return false;
    15 }
    16 
    17 private void removeViewInternal(int index, View view) {
    18     if (mTransition != null) {
    19         mTransition.removeChild(this, view);
    20     }
    21 
    22     boolean clearChildFocus = false;
    23     if (view == mFocused) {
    24         view.unFocus(null);
    25         clearChildFocus = true;
    26     }
    27     if (view == mFocusedInCluster) {
    28         clearFocusedInCluster(view);
    29     }
    30 
    31     view.clearAccessibilityFocus();
    32 
    33     cancelTouchTarget(view);
    34     cancelHoverTarget(view);
    35 
    36     if (view.getAnimation() != null ||
    37             (mTransitioningViews != null && mTransitioningViews.contains(view))) {
    38         addDisappearingView(view);
    39     } else if (view.mAttachInfo != null) {
    40        view.dispatchDetachedFromWindow();
    41     }
    42 
    43     if (view.hasTransientState()) {
    44         childHasTransientStateChanged(view, false);
    45     }
    46 
    47     needGlobalAttributesUpdate(false);
    48 
    49     removeFromArray(index);
    50 
    51     if (view == mDefaultFocus) {
    52         clearDefaultFocus(view);
    53     }
    54     if (clearChildFocus) {
    55         clearChildFocus(view);
    56         if (!rootViewRequestFocus()) {
    57             notifyGlobalFocusCleared(this);
    58         }
    59     }
    60 
    61     dispatchViewRemoved(view);
    62 
    63     if (view.getVisibility() != View.GONE) {
    64         notifySubtreeAccessibilityStateChangedIfNeeded();
    65     }
    66 
    67     int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    68     for (int i = 0; i < transientCount; ++i) {
    69         final int oldIndex = mTransientIndices.get(i);
    70         if (index < oldIndex) {
    71             mTransientIndices.set(i, oldIndex - 1);
    72         }
    73     }
    74 
    75     if (mCurrentDragStartEvent != null) {
    76         mChildrenInterestedInDrag.remove(view);
    77     }
    78

         同样的,也有查找获得孩子节点的函数: 

        /**
         * Returns the view at the specified position in the group.
         *
         * @param index the position at which to get the view from
         * @return the view at the specified position or null if the position
         *         does not exist within the group
         */
        public View getChildAt(int index) {
            if (index < 0 || index >= mChildrenCount) {
                return null;
            }
            return mChildren[index];
        } 

        注:其中具体叶子节点,如Button,它是继承TextView的,TextView是继承View的,代码如下: 

    public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
        ......
    } 

        注:其中使用(继承)到ViewGroup类的有我们常用的容器类(包装和容纳各种View),如LinearLayout、FrameLayout等,代码如下: 

    public class LinearLayout extends ViewGroup {
        public static final int HORIZONTAL = 0;
        public static final int VERTICAL = 1;
        ......
    } 
    public class FrameLayout extends ViewGroup {
      ...
    }

    public class RelativeLayout extends ViewGroup {
      private static final String LOG_TAG = "RelativeLayout";

      private static final boolean DEBUG_GRAPH = false;
      ...
    }

    public class AbsoluteLayout extends ViewGroup {

      public AbsoluteLayout(Context context) {
         super(context);
      }

    }
     

    四、基本控件继承关系图 

        最后送上“基本控件继承关系图”:

     

  • 相关阅读:
    14个你可能不知道的JavaScript调试技巧
    数据库设计四步骤
    mac 卸载 jdk
    node版本管理
    mysql order by limit 问题
    计算机一些基本概念的认识
    SQL设置主外键关联时报错
    阻止表单autocomplete
    常见字符编码
    编程语言分类
  • 原文地址:https://www.cnblogs.com/linghu-java/p/5728308.html
Copyright © 2011-2022 走看看