十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#

组合具有一致性…

故事背景

坚持去输出真的很不容易,今天的的天气真的是热啊!我之前一直想些一个系列是和设计模式去旅行,通过构思一些场景,让自己更好的理解和表达设计模式,但是有时候为了思考一个适合的故事会花费很多时间,so,从这里开始,如果后面的设计模式想到了好的场景的话就写故事背景,要不就简单介绍,重点看故事主角。

在现实生活中很多地方我们会使用到树形结构,在软件中也随处可见,例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题。

本篇组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)。

故事主角

组合模式:组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(既容器对象)的使用具有一致性。

组合模式类图

在组合模式机构中有如下几个角色:
- Component(抽象组件): 可以是接口或者抽象类,为叶子构件和容器对象声明接口,在该角色中科院包含所有子类共有行为的声明和实现。
- leaf(叶子构件):组合结构中的叶子节点对象。
- Composite(容器构件):组合机构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点。

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。

// 抽象构件类
abstract class Component {
    public abstract void add(Component c); //增加成员
    public abstract void remove(Component c); //删除成员
    public abstract Component getChild(int i); //获取成员
    public abstract void operation();  //业务方法
}
// 叶子构件
class Leaf extends Component {
    public void add(Component c) { 
        //异常处理或错误提示 
    }   

    public void remove(Component c) { 
        //异常处理或错误提示 
    }

    public Component getChild(int i) { 
        //异常处理或错误提示
        return null; 
    }

    public void operation() {
        //叶子构件具体业务方法的实现
    } 
}

//容器构件
class Composite extends Component {
    private ArrayList<Component> list = new ArrayList<Component>();

    public void add(Component c) {
        list.add(c);
    }

    public void remove(Component c) {
        list.remove(c);
    }

    public Component getChild(int i) {
        return (Component)list.get(i);
    }

    public void operation() {
        //容器构件具体业务方法的实现
        //递归调用成员构件的业务方法
        for(Object obj:list) {
            ((Component)obj).operation();
        }
    }   
}

武功修炼

通过对组合模式的认识,通过一个例子来进行学习。
在计算机系统中有文件系统,也就是文件夹和文件,在文件夹中既可以放入文件也可以放入文件夹,文件夹是形成一种容器结构,递归递归。

组合模式简化图

public class FileTreatmentException extends RuntimeException {

    public FileTreatmentException(){}

    public FileTreatmentException(String msg){
        super(msg);
    }
}


// 抽象类,用来实现File类和Directory 类的一致性
// 透明方式实现
public abstract class Entry {

    /**
     *获取名称
     * @return
     */
    public abstract String getName();

    /**
     * 获取大小
     * @return
     */
    public abstract int getSize();

    /**
     * 显示目录条目
     * @param entry
     * @return
     * @throws FileTreatmentException
     */
    public Entry add(Entry entry) throws FileTreatmentException{
        throw new FileTreatmentException();
    }

    public void printList(){
        printList("");
    }

    public abstract void printList(String prefix);

    @Override
    public String toString() {
        return getName() + "(" + getSize() + ")";
    }
}

public class File extends Entry {

    private String name;
    private int size;

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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    public void printList(String prefix) {
        System.out.println(prefix + "/" + this);
    }
}

public class Directory extends Entry {

    private String name;// 文件夹名字

    private List directory = new ArrayList(); // 文件夹目录集合


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

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        Iterator it = directory.iterator();
        // 这里使用了迭代器模式
        while(it.hasNext()){
            Entry entry = (Entry) it.next();
            size += entry.getSize();
        }

        return size;
    }

    @Override
    public Entry add(Entry entry) throws FileTreatmentException {
        directory.add(entry);
        return this;
    }

    @Override
    public void printList(String prefix) {
        System.out.println(prefix + "/" + this);
        Iterator it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = (Entry) it.next();
            entry.printList(prefix + "/" + name);

        }
    }
}
public class TestClient {

    public static void main(String[] args) {

        Directory rootDir = new Directory("root");
        Directory binDir = new Directory("bin");
        Directory confDir = new Directory("conf");
        Directory tempDir = new Directory("temp");

        rootDir.add(binDir);
        rootDir.add(confDir);
        rootDir.add(tempDir);

        binDir.add(new File("javac", 100));
        binDir.add(new File("jstac", 1000));

        confDir.add(new File("nginx.conf", 20));


        rootDir.printList();

    }


}


/root(1120)
/root/bin(1100)
/root/bin/javac(100)
/root/bin/jstac(1000)
/root/conf(20)
/root/conf/nginx.conf(20)
/root/temp(0)


总结

优点
- 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
-客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
-组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

缺点
- 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

适用场景
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。

  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构。

在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

Next 期待下一篇吧!下一篇讲讲状态模式!

参考


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

猜你喜欢

转载自blog.csdn.net/u010648555/article/details/81052258