组合模式的定义如下:将对象组合成树形结构以表示“部分-整体”的层次结构,让用户对单个对象和组合对象的使用具有一致性。
适用组合模式的情景如下:
- 希望表示对象的“部分—整体”层次结构
- 希望用户用一致方式处理个体和组合对象
一、问题的提出
我们研究的问题有许多树形结构的问题,例如文件结构:
例如,要用程序创建文件结构,为了验证正确与否,还要再控制台上输出从某目录开始的所有文件信息。文件树形结构可以分为两类,一类是文件叶子节点,无后继节点,一类是中间目录节点,有后继节点。具体代码如下:
(1)文件节点类
public class FileLeaf {
String fileName;
public FileLeaf(String fileName) {
this.fileName = fileName;
}
public void display() {
System.out.println(fileName);
}
}
(2)中间目录节点类 DirectNode
public class DirectNode {
String nodeName;
public DirectNode(String nodeName) {
this.nodeName = nodeName;
}
//后继目录集合
ArrayList<DirectNode> nodeList = new ArrayList<>();
//当前目录文件集合
ArrayList<FileLeaf> fileList = new ArrayList<>();
//添加下一级子目录
public void addNode(DirectNode node ) {
nodeList.add(node);
}
//添加本级文件
public void addLeaf(FileLeaf leaf) {
fileList.add(leaf);
}
//从本级目录开始显示
public void display() {
for (int i = 0; i < fileList.size(); i++) {
fileList.get(i).display();
}
for (int i = 0; i < nodeList.size(); i++) {
System.out.println(nodeList.get(i).nodeName);
nodeList.get(i).display();
}
}
}
(3)测试类
public class Test {
public static void createTree(DirectNode node) {
File f = new File(node.nodeName);
File f2[] = f.listFiles();
for (int i = 0; i < f2.length; i++) {
//如果是文件类型,则把他添加到当前目录文件集合
if (f2[i].isFile()) {
FileLeaf l = new FileLeaf(f2[i].getAbsolutePath());
node.addLeaf(l);
}
//如果是目录类型,则把他添加到目录集合,然后继续递归添加
if (f2[i].isDirectory()) {
DirectNode node2 = new DirectNode(f2[i].getAbsolutePath());
node.addNode(node2);
createTree(node2);
}
}
}
public static void main(String[] args) {
DirectNode start = new DirectNode("D:\\学习笔记\\Linux\\docker-book-master\\docker");
createTree(start);
start.display();
}
}
二、组合模式
从上面图片可知:根目录是由两个子目录组成的;第一个子目录由两个文件组成;第二个子目录也由两个文件组成,因此树形形式也可以叫做组合模式。
在图中,把节点分为叶子节点与目录节点,它们是孤立的。然后把叶子节点与目录节点都看成相同性质的节点,只不过目录节点的后继节点不为空,而叶子节点的后继节点为null。这样就能够对树形结构的所有节点执行相同的操作,这也是组合模式最大的特点。
采用组合模式修改上面例子的功能:
(1)定义抽象节点类Node
该类是叶子节点与目录节点的父类,节点名称是name。其主要包括两类方法:一类方法是所有节点具有相同形式、不同内容的方法。这类方法要定义成抽象方法,如display();另一类方法是目录节点必须重写,而叶子节点不需要重写的方法,相当于为叶子节点提供了默认实现,如addNode()方法。因为叶子对象没有该功能,所以可以通过抛出异常防止叶子节点无效调用该方法。/
public abstract class Node {
protected String name;
public Node(String name) {
this.name = name;
}
public void addNode(Node node) throws Exception {
throw new Exception("无效的异常");
}
abstract void display();
}
(2)文件叶子节点类 FileNode
public class FileNode extends Node{
public FileNode(String name) {
super(name);
}
@Override
void display() {
System.out.println(name);
}
}
(3)目录节点类 DirectNode
该类从Node抽象类派生后,与原DirectNode类相比,主要有以下不同:
- 由定义两个结合类成员变量转为定义一个集合类成员变量nodeList
- 由定义两个添加方法转为定义一个添加方法addNode()
- display() 方法中,由两个不同元素的循环转为一个对相同性质节点Node循环。
public class DirectNode2 extends Node{
private ArrayList<Node> nodeList = new ArrayList<>();
public DirectNode2(String name) {
super(name);
}
public void addNode(Node node) throws Exception {
nodeList.add(node);
}
@Override
void display() {
System.out.println(name);
for (int i = 0; i < nodeList.size(); i++) {
nodeList.get(i).display();
}
}
}
(4) 测试类
public class Test2 {
public static void createTree(Node node) throws Exception {
File f = new File(node.name);
File f2[] = f.listFiles();
for (int i = 0; i < f2.length; i++) {
if (f2[i].isFile()) {
Node node2 = new FileNode(f2[i].getAbsolutePath());
node.addNode(node2);
}
if (f2[i].isDirectory()) {
Node node2 = new DirectNode2(f2[i].getAbsolutePath());
node.addNode(node2);
createTree(node2);
}
}
}
public static void main(String[] args) throws Exception {
Node start = new DirectNode2("D:\\学习笔记\\Linux\\docker-book-master\\docker");
createTree(start);
start.display();
}
}