Android 源码中的组合模式

1,974 阅读4分钟

从装饰者模式到Context类族

当观察者模式和回调机制遇上Android源码

Android源码中的静态工厂方法

Android中的工厂方法模式

Android源码中的命令模式

Android源码中的适配器模式

Android源码中的外观模式

不只是迭代器模式

定义

允许将对象组成树形结构来表现“整体-部分”的层次结构。组合能让客户以一致的方式处理个别对象和对象组合。属于结构型设计模式。

使用场景

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

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

结构

模式所涉及的角色有:

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

这个接口可以用来管理所有的子对象。(可选)在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。

树叶构件角色(Leaf):在组合树中表示叶节点对象,叶节点没有子节点。并在组合中定义图元对象的行为。

树枝构件角色(Composite):定义有子部件的那些部件的行为。存储子部件。在Component接口中实现与子部件有关的操作。

客户角色(Client):通过component接口操纵组合部件的对象。

实现

通过前边的使用场景说明,我们发现文件结构正好就是一个整体-部分层次的树形结构。那如果我们想知道文件名,我们好像并不需要管他是文件还是文件夹,所以我们要想办法忽略不同层次对象的差异。接下来我们看下实现:

抽象构件角色,即文件类的抽象接口

/**
 * 文件抽象类
 */

public abstract class File {

    private String name;

    public File(String name) {
        this.name = name;
    }

    //操作方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract void watch();

    //组合方法
    public void add(File file) {
        throw new UnsupportedOperationException();
    }

    public void remove(File file) {
        throw new UnsupportedOperationException();
    }

    public File getChild(int position) {
        throw new UnsupportedOperationException();
    }
}

树叶构件角色,即我们的具体格式文件,比如文本文件

/**
 * 文本文件
 */

public class TextFile extends File {

    public TextFile(String name) {
        super(name);
    }

    @Override
    public void watch() {
        Log.e("组合模式", "这是一个叫" + getName() + "文本文件");
    }
}

树枝构件角色,即我们的文件夹

/**
 * 文件夹类
 */

public class Folder extends File {

    private List mFileList;

    public Folder(String name) {
        super(name);
        mFileList = new ArrayList<>();
    }

    @Override
    public void watch() {
        StringBuffer fileName = new StringBuffer();
        for (File file : mFileList) {
            fileName.append(file.getName() + ";");
        }
        Log.e("组合模式", "这是一个叫" + getName() + "文件夹,包含" + mFileList.size() + "个文件,分别是:" + fileName);
    }

    @Override
    public void add(File file) {
        mFileList.add(file);
    }

    @Override
    public void remove(File file) {
        mFileList.remove(file);
    }

    @Override
    public File getChild(int position) {
        return mFileList.get(position);
    }
}

客户端

/**
 * 测试组合模式
 */
private void testComposite() {
    TextFile textFileA = new TextFile("a.txt");
    TextFile textFileB = new TextFile("b.txt");
    TextFile textFileC = new TextFile("c.txt");

    textFileA.watch();
//  textFileA.add(textFileB);//调用会抛我们在抽象接口中写的异常

    Folder folder = new Folder("学习资料");
    folder.add(textFileA);
    folder.add(textFileB);
    folder.add(textFileC);

    folder.watch();
    folder.getChild(1).watch();
}

这里有一点是我们的抽象构件中包含组合方法和操作方法,有一些操作方法是树叶构件和树枝构件都会实现的,而组合方法是需要树枝构件来实现的,即我们对树叶构件的增删等操作。

Android源码中的组合模式

接下来我们思考一下Android源码中组合模式的应用,组合模式的使用场景是:表示一个对象整体-部分层次结构,即树形结构。而Android中典型的树形结构不就是我们的View体系么。在前边的模板方法模式中我们已经画过view的结构了,这里就不再画了,我们来一起分析一下:

Android源码中View是一个类(不是抽象类,不是接口),即我们的抽象构件。ViewGroup是View的子类,是一个抽象类,作为所有View的容器,即我们的树枝构件。而具体到特定的控件,比如Button、TextView等是我们的树叶构件。

通过观察我们的View类中是没有封装添加删除View的操作的,那么它们是在哪里添加到ViewGroup中的呢?我们发现ViewGroup实现了一个叫ViewManager的接口:

/** 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); }

ViewGroup实现这个接口的方法,所有就有了管理View的能力。

那么Google的开发者为什么要这么设计Android的View体系呢?组合模式可以让高层模块忽略了层次的差异,方便对整个层次结构进行控制。比如我们要向ViewGoup添加一个TextView或者添加一个LinearLayout,对这个ViewGroup来说是一样的。

搞定,收工。

测试代码已上传到github