设计模式-组合模式

组合模式

组合模式是一种结构型设计模式,它的使用形式比较固定,适合用来表示树形结构,或者说具有层级结构的数据。

比如目录和文件,xml等。

What?

将一组对象组织(Compose)成树形结构,以表示一种“部分 - 整体”的层次结构。组合让客户端(在很多设计模式书籍中,“客户端”代指代码的使用者。)可以统一单个对象和组合对象的处理逻辑。

How?

练习1:如何统计目录下的文件数?

以目录和文件为例,给定一个需求:想要统计目录下有多少文件。

文件结构:

如何实现?

首先,我们抽象出一个FileSystemNode.class类,统一表示文件和目录

public abstract class FileSystemNode {
    private String path;

    public FileSystemNode(String path) {
        this.path = path;
    }

    public String getPath() {
        return path;
    }
    /**
     * 统计指定目录下的文件个数
     *
     * @return
     */
    public abstract int countNumOfFiles();

}

再定义文件类

public class File extends FileSystemNode {
    public File(String path) {
        super(path);
    }

    @Override
    public int countNumOfFiles() {
        return 1;
    }
}

文件时叶子节点,统计文件的个数,直接返回1即可。

最后定义目录类

public class Directory extends FileSystemNode {
    private List<FileSystemNode> subNodes = new ArrayList<>();

    public Directory(String path) {
        super(path);
    }

    @Override
    public int countNumOfFiles() {
        int numOfFiles = 0;
        for (FileSystemNode subNode : subNodes) {
            numOfFiles += subNode.countNumOfFiles();
        }
        return numOfFiles;
    }

    public void addSubNode(FileSystemNode fileOrDir) {
        subNodes.add(fileOrDir);
    }
	/**
	* 删除子节点
	*/
    public void removeSubNode(FileSystemNode fileOrDir) {
        int size = subNodes.size();
        int i = 0;
        for (; i < size; ++i) {
            if (subNodes.get(i).getPath().equalsIgnoreCase(fileOrDir.getPath())) {
                break;
            }
        }
        if (i < size) {
            subNodes.remove(i);
        }
    }
}

目录节点根节点,它维护一个list来存子节点,子节点也是FileSystemNode类。

在统计文件个数countNumOfFiles的时候,如果是文件节点,直接遍历其子节点的countNumOfFiles方法即可。

根节点还维护了添加子节点和删除子节点的方法。

客户端调用

public static void main(String[] args) {
        /**
         * /
         * /wz/
         * /wz/a.txt
         * /wz/b.txt
         * /wz/movies/
         * /wz/movies/c.avi
         * /xzg/
         * /xzg/docs/
         * /xzg/docs/d.txt
         */
        Directory fileSystemTree = new Directory("/");
        Directory node_wz = new Directory("/wz/");
        Directory node_xzg = new Directory("/xzg/");
        fileSystemTree.addSubNode(node_wz);
        fileSystemTree.addSubNode(node_xzg);

        File node_wz_a = new File("/wz/a.txt");
        File node_wz_b = new File("/wz/b.txt");
        Directory node_wz_movies = new Directory("/wz/movies/");
        node_wz.addSubNode(node_wz_a);
        node_wz.addSubNode(node_wz_b);
        node_wz.addSubNode(node_wz_movies);

        File node_wz_movies_c = new File("/wz/movies/c.avi");
        node_wz_movies.addSubNode(node_wz_movies_c);

        Directory node_xzg_docs = new Directory("/xzg/docs/");
        node_xzg.addSubNode(node_xzg_docs);

        File node_xzg_docs_d = new File("/xzg/docs/d.txt");
        node_xzg_docs.addSubNode(node_xzg_docs_d);

        System.out.println("/ files num:" + fileSystemTree.countNumOfFiles());
        System.out.println("/wz/ files num:" + node_wz.countNumOfFiles());
    }

输出

/ files num:4
/wz/ files num:3

组合模式把具有层级关系的树节点用一个统一的数据结构表示(FileSystemNode),方便扩展(扩展新的类只要继承或者实现统一的抽象即可),代码可读性强。

练习2:用树结构表示xml或者html

首先抽象出一个节点的概念,表示xml的标签,它可以统一表示xml的标签,文本或者注释

public interface Node {
    String toXml();
    Node addNode(Node node);
}

文本的表示:

public class TextNode implements Node {
    private String text;

    public TextNode(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    @Override
    public String toXml() {
        return text + "
";
    }

    @Override
    public Node addNode(Node node) {
        throw new UnsupportedOperationException();
    }
}

文本的toXml,直接返回自身的text内容即可;文本节点不能再addNode,抛异常

标签的表示:

public class ElementNode implements Node {
    private String name;
    private List<Node> childList = new ArrayList<>();

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

    public String getNodeName() {
        return this.name;
    }

    @Override
    public String toXml() {
        StringBuilder builder = new StringBuilder();
        builder.append("<").append(this.name).append(">
");
        for (Node child : this.childList) {
            builder.append(child.toXml());
        }
        builder.append("</").append(this.name).append(">
");
        return builder.toString();
    }

    @Override
    public Node addNode(Node node) {
        if (null == node) {
            return null;
        }
        this.childList.add(node);
        return this;
    }
}

根节点需要维护一个子节点的list。 标签的toXml方法直接遍历这个list节点的toXml方法即可。

客户端调用:

public class Test {
    public static void main(String[] args) {
        ElementNode root = new ElementNode("root");
        ElementNode html = new ElementNode("html");
        ElementNode head = new ElementNode("head");
        ElementNode body = new ElementNode("body");
        TextNode textNode = new TextNode("This is a text!");
        body.addNode(textNode);
        html.addNode(head);
        html.addNode(body);
        root.addNode(html);
        String s = root.toXml();
        System.out.println(s);
    }
}

输出:

<root>
<html>
<head>
</head>
<body>
This is a text!
</body>
</html>
</root>

如果想增加一个注释节点(CommentNode),只需要实现Node接口即可

总结

组合模式适合于树结构的表示,能够方便树结构的操作(遍历),能够提供更好的扩展性,且代码更加易读。Tomcat的多级容器实现的Container接口也是组合模式的实现。

原文地址:https://www.cnblogs.com/SimonZ/p/14546265.html