前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式学习之组合模式

设计模式学习之组合模式

作者头像
老马的编程之旅
发布2022-06-22 11:04:27
4840
发布2022-06-22 11:04:27
举报
文章被收录于专栏:深入理解Android

组合模式(Composite Pattern),是结构型模式之一。组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。这个最典型的例子就是数据结构中的树,本篇博客我们就一起学习组合模式。

定义与使用场景

定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

使用场景: 1.表示对象的部分-整体层次结构时。

2.从一个整体中能够独立出部分模块或功能的场景。

UML图

(1)Component:抽象根节点,为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它。

(2)Composite:定义有子节点的那些枝干节点行为,存储子节点,在Component接口中实现与子节点有关的操作。

(3)Leaf:在组合中表示叶子节点对象,叶子节点没有子节点,在组合中定义节点对象的行为。

(4)Client:通过Component接口操纵组合节点的对象。

组合模式模版代码

组合模式在实际使用中会有两种情况:安全的组合模式与透明的组合模式。上面的UML图属于安全组合模式的UML图。

安全组合模式 Component.class

代码语言:javascript
复制
public abstract class Component {
    public abstract void operation();
}

Composite.class

代码语言:javascript
复制
public class Composite extends Component{

    private ArrayList<Component> componentList = new ArrayList<>();

    @Override
    public void operation() {

        for (Component component : componentList) {
            component.operation();
        }

    }

    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

代码语言:javascript
复制
public class Leaf extends Component{
    @Override
    public void operation() {

    }
}

测试代码:

代码语言:javascript
复制
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;

可以发现,安全组合模式违反了 6 个设计模式原则中依赖倒置原则,客户端不应该直接依赖于具体实现,而应该依赖于抽象,既然是面向接口编程,就应该把更多的焦点放在接口的设计上,于是这样就产生了透明的组合模式。

透明的组合模式 透明的组合模式 uml 类图如下:

和安全的组合模式差异就是在将 Composite 的操作放到了 Component 中,这就造成 Leaf 角色也要实现 Component 中的所有方法。实现的代码做出相应改变:

Component.class

代码语言:javascript
复制
public interface Component {
    void operation();

    void add(Component child);

    void remove(Component child);

    Component getChild(int position);
}

Composite.class

代码语言:javascript
复制
public class Composite implements Component{

    private ArrayList<Component> 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

代码语言:javascript
复制
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");
    }
}

测试代码

代码语言:javascript
复制
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)

代码语言:javascript
复制
public abstract class Dir {
    /**
     * 声明一个List成员变量来储存文件夹下的所有元素
     */
    protected List<Dir> dirs = new ArrayList<Dir>();

    private String name; //当前文件或文件夹名

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

    /**
     * 添加一个文件或文件夹
     * 
     * @param dir 文件或文件夹
     */
    public abstract void addDir(Dir dir);

    /**
     * 移除一个文件或文件夹
     * 
     * @param dir 文件或文件夹
     */
    public abstract void rmDir(Dir dir);

    /**
     * 清空文件夹下所有元素
     */
    public abstract void clear();

    /**
     * 输出文件夹目录结构
     */
    public abstract void print();

    /**
     * 获取文件夹下所有的文件或文件夹
     * 
     * @return 文件夹下所有的文件或文件夹
     */
    public abstract List<Dir> getFiles();

    /**
     * 获取文件或文件夹名
     * 
     * @return 文件或文件夹名
     */
    public String getName(){
        return name;
    }
}

表示文件夹的类:(Composite)

代码语言:javascript
复制
public class Folder extends Dir{

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

    @Override
    public void addDir(Dir dir) {
        dirs.add(dir);
    }

    @Override
    public void rmDir(Dir dir) {
        dirs.remove(dir);
    }

    @Override
    public void clear() {
        dirs.clear();
    }

    @Override
    public void print() {
        System.out.print(getName() + "(");
        Iterator<Dir> iter = dirs.iterator();
        while (iter.hasNext()) {
            Dir dir = iter.next();
            dir.print();
            if(iter.hasNext()){
                System.out.print(", ");
            }
        }
        System.out.print(")");
    }

    @Override
    public List<Dir> getFiles() {
        return dirs;
    }

}

表示文件夹的类:(Leaf)

代码语言:javascript
复制
public class File extends Dir{

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

    @Override
    public void addDir(Dir dir) {
        throw new UnsupportedOperationException("文件对象不支持该操作!");
    }

    @Override
    public void rmDir(Dir dir) {
        throw new UnsupportedOperationException("文件对象不支持该操作!");
    }

    @Override
    public void clear() {
        throw new UnsupportedOperationException("文件对象不支持该操作!");
    }

    @Override
    public void print() {
        System.out.print(getName());
    }

    @Override
    public List<Dir> getFiles() {
        throw new UnsupportedOperationException("文件对象不支持该操作!");
    }

}

客户类:

代码语言:javascript
复制
public class Client {
    public static void main(String[] args) {
        //构造一个目录对象表示C盘根目录
        Dir diskC = new Folder("C");

        //C盘根目录下有一个文件Log.txt
        diskC.addDir(new File("Log.txt"));

        //C盘根目录下有三个目录Windows、PerfLogs、Program File
        Dir dirWin = new Folder("Windows");

        //Windows目录下有文件explorer.exe
        dirWin.addDir(new File("explorer.exe"));
        diskC.addDir(dirWin);

        //PerfLogs目录
        Dir dirPer = new Folder("PerfLogs");

        //PerfLogs目录下有文件null.txt
        dirPer.addDir(new File("null.txt"));
        diskC.addDir(dirPer);

        //Program File目录
        Dir dirPro = new Folder("Program File");

        //Program File目录下有文件ftp.txt
        dirPro.addDir(new File("ftp.txt"));
        diskC.addDir(dirPro);

        //打印出文件结构
        diskC.print();
    }
}

Android源码中的模式实现

1.View和ViewGroup的嵌套组合

View和ViewGroup就是组合模式的实现,不过View的视图层级使用的是安全的组合模式。ViewGroup相对于View增加了addView、removeView、getChildAt等方法,想必大家也很熟悉。

总结

1.优点

(1)组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,他让高层模块忽略了层次的差异,方便对整个层次结构进行控制。

(2)简化了高层模块的代码。

(3)在组合模式中增加新的枝干构件和叶子构件都很方便,无须对现有类库进行修改,符合“开闭原则”。

(4)对树形结构的控制变得简单。

2.缺点 组合模式不容易限制组合中的构件。因为大多数情况下,它们都来自于相同的抽象层,此时,必须进行类型检查来实现,这个实现过程较为复杂。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义与使用场景
  • UML图
  • 组合模式简单实现
  • Android源码中的模式实现
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档