简介
- 定义:将对象组合成树形结构以表示“部分——整体”的层次结构。
- 说明:组合模式使客户端对单个对象和组合对象保持一致的处理方式
- 类型:结构型
- 适用场景
- 希望客户端可以忽略组合对象与单个对象的差异时
- 处理一个树形结构时
- 优点:
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 简化客户端代码
- 符合开闭原则
- 缺点:
- 限制类型时较为复杂
- 使设计变得更加抽象
- 相关设计模式
- 组合模式和访问者模式
代码实现
业务场景:通过定义“将对象组合成树形结构”,那么我们以常见的树形结构作为简单的例子——文件(File)与目录(Folder)
我们把树形结构上的每个节点都抽象为一个组件Component。也就是说在这颗树上的节点,无论是文件(File)还是目录(Folder),都是一个组件(Component)。
于是我们可以定义组件(Component)的抽象类。让File和Folder继承自这个组件抽象类。
下面是抽象组件类Node:
/**
* 抽象组件类:节点。
*/
public abstract class Node {
/**
* 获取节点的名称。
* @return 例子中,表示Folder或者File的名称
*/
public abstract String getName();
/**
* 节点的属性(简单地用String作为返回值类型)。
* @return 例子中,表示Folder或者File的属性
*/
public abstract String getProperty();
/**
* Folder 会重写这个方法
* File 不会重写这个方法。所以默认,允许调用父类的方法
* @param component 需要添加的子节点(子组件)
*/
public void add(Node component){
throw new UnsupportedOperationException("不能进行add操作");
}
/**
* 跟上面的add调用方式是一样的
* @param component 需要删除的子节点(子组件)
*/
public void delete(Node component){
throw new UnsupportedOperationException("不能进行delete操作");
}
/**
* 打印出此节点的信息,由具体子类(File,Folder)来实现
*/
public abstract void print();
}
文件类, 因为他也是一棵树上的节点(组建),所以继承自Node类:
/**
* 文件类。
*/
public class File extends Node {
// 文件名和属性
private String name;
private String property;
public File(String name, String property) {
this.name = name;
this.property = property;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getProperty() {
return this.property;
}
@Override
public void print() {
System.out.println( "→{File:" + this.getName() + "," + this.getProperty() + "}" );
}
/**
* 我们不需要实现add,delete方法。
* 因为文件(File)并不会存在子节点(组件)。
* 同时File对象不能调用add,delete方法,否则会报错
*/
}
同样文件类,也继承自Node抽象组件类:
/**
* 文件组件。它同样是一个节点
*/
public class Folder extends Node {
// 文件(Folder)的属性和名称
private String name;
private String property;
// 一个目录下应该会有多个目录或者文件(一个根节点下会有多个子节点)
private List<Node> childNode = new ArrayList<>();
public Folder(String name, String property) {
this.name = name;
this.property = property;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getProperty() {
return this.property;
}
@Override
public void print() {
print(this, 0);
}
/**
* 这里的print私有函数用来打印此目录的目录结构。
* 如果是子节点是目录的话,递归地使用print
* 如果子节点是文件的话,直接print就行
* 其中打印时前缀:↓ 表示目录,→ 表示文件
* @param node
* @param depth
*/
private void print(Node node, int depth){
//为了显示目录层级关系而打印空白
for(int i = 0 ; i < depth ; i++)
System.out.print(" ");
// 如果是文件,那么直接打印返回就好
if(node instanceof File){
node.print();
// 或者
// System.out.println( "→{File:" + node.getName() + "," + node.getProperty() + "}" );
// 注意return了
return;
}
// 如果是是Folder(目录)的话,↓
System.out.println( "↓{Folder:" + node.getName() + "," + node.getProperty() + "}" );
// 目录下如果还有Folder或者File
for(Node child: ((Folder)node).childNode ){
// 如果是文件,直接打印文件就好
if(child instanceof File) {
print(child, depth+1);
}
else //如果是目录
((Folder)child).print(child, depth+1);
}
}
@Override
public void add(Node node) {
childNode.add(node);
}
@Override
public void delete(Node node) {
childNode.remove(node);
}
}
对系统进行测试,测试类Test:
/**
* 最系统进行测试,Test类
*/
public class Test {
public static void main(String[] args) {
/** 测试,目录结构如下。
* 其中 > 表示该对象为目录(Folder)
* 反之则为文件(File)
* 看目录应该很容易理解此树结构
> /
RootSummaryFile.File
> RootEmptyFolder
> studyFolder
studySummary.File
> studyEmptyFolder
> studyJava
studyJavaFile.File
> studyC
studyCFile.File
> GameFolder
gameSummary
> gameEmptyFolder
> game1
game1File.File
> game2
game2File.File
*******/
/****************
*下面是两个测试函数,对系统进行测试
***************/
// print1,显示了文件的属性描述
print1();
System.out.println("==============================================");
// print2,不显示文件的属性描述,目录结构更清晰
print2();
}
public static void print1(){
Node root = new Folder("/", "根目录");
/******学习目录******/
Node studyFolder = new Folder("Study", "学习目录");
Node studySummary = new File("SummaryAboutStudy","学习目录下的总概要");
Node studyJava = new Folder("StudyJava","学习java的学习目录");
Node studyJavaFile = new File("StudyJavaFile.file", "学习java目录下文件");
studyFolder.add(studySummary);
studyJava.add(studyJavaFile);
studyFolder.add(studyJava);
Node studyC = new Folder("StudyC", "学习C语言的目录");
Node studyCFile = new File("StudyCFile.file", "学习C语言目录下的文件");
studyC.add(studyCFile);
studyFolder.add(studyC);
/****** 游戏目录 ******/
Node gameFolder = new Folder("Game", "游戏目录");
Node gameSummary = new File("SummaryAboutSummary","游戏目录下的总概要");
Node game1 = new Folder("Game1","游戏1的目录");
Node game1File = new File("Game1File", "游戏1目录下的文件");
gameFolder.add(gameSummary);
game1.add(game1File);
gameFolder.add(game1);
Node game2 = new Folder("Game2", "游戏2的目录");
Node game2File = new File("Game2File", "游戏2目录下文件");
game2.add(game2File);
gameFolder.add(game2);
root.add( new File("RootSummaryFile.File", "根目录下的文件") );
root.add( new Folder("RootEmptyFolder", "根目录下的空目录") );
root.add(studyFolder);
root.add(gameFolder);
root.print();
}
public static void print2(){
Node root = new Folder("/", "");
/******学习目录******/
Node studyFolder = new Folder("Study", "");
Node studySummary = new File("SummaryAboutStudy","");
Node studyJava = new Folder("StudyJava","");
Node studyJavaFile = new File("StudyJavaFile.file", "");
studyFolder.add(studySummary);
studyJava.add(studyJavaFile);
studyFolder.add(studyJava);
Node studyC = new Folder("StudyC", "");
Node studyCFile = new File("StudyCFile.file", "");
studyC.add(studyCFile);
studyFolder.add(studyC);
/****** 游戏目录 ******/
Node gameFolder = new Folder("Game", "");
Node gameSummary = new File("SummaryAboutSummary","");
Node game1 = new Folder("Game1","");
Node game1File = new File("Game1File", "");
gameFolder.add(gameSummary);
game1.add(game1File);
gameFolder.add(game1);
Node game2 = new Folder("Game2", "");
Node game2File = new File("Game2File", "");
game2.add(game2File);
gameFolder.add(game2);
root.add( new File("RootSummaryFile.File", "") );
root.add( new Folder("RootEmptyFolder", "") );
root.add(studyFolder);
root.add(gameFolder);
root.print();
}
}
输出测试用例:
↓{Folder:/,根目录}
→{File:RootSummaryFile.File,根目录下的文件}
↓{Folder:RootEmptyFolder,根目录下的空目录}
↓{Folder:Study,学习目录}
→{File:SummaryAboutStudy,学习目录下的总概要}
↓{Folder:StudyJava,学习java的学习目录}
→{File:StudyJavaFile.file,学习java目录下文件}
↓{Folder:StudyC,学习C语言的目录}
→{File:StudyCFile.file,学习C语言目录下的文件}
↓{Folder:Game,游戏目录}
→{File:SummaryAboutSummary,游戏目录下的总概要}
↓{Folder:Game1,游戏1的目录}
→{File:Game1File,游戏1目录下的文件}
↓{Folder:Game2,游戏2的目录}
→{File:Game2File,游戏2目录下文件}
=======================
↓{Folder:/,}
→{File:RootSummaryFile.File,}
↓{Folder:RootEmptyFolder,}
↓{Folder:Study,}
→{File:SummaryAboutStudy,}
↓{Folder:StudyJava,}
→{File:StudyJavaFile.file,}
↓{Folder:StudyC,}
→{File:StudyCFile.file,}
↓{Folder:Game,}
→{File:SummaryAboutSummary,}
↓{Folder:Game1,}
→{File:Game1File,}
↓{Folder:Game2,}
→{File:Game2File,}