组合模式,Composite,Java实现
组合模式-Composite
Composite模式使得用户对单个对象和组合对象的使用具有一致性.
以<<图解设计模式>>的文件例子来说: 文件夹是文件的组合, 文件是单个对象, 文件夹是多个文件的组合. 不过对用户来说, 无论是文件还是文件夹, 他希望使用一个统一的方法来管理他们.这就需要将他们再次抽象出来.
解读一下这个类图就明白了:
1. 文件是一个组件
2. 文件夹也是一个组件
3. 文件夹里有很多组件:
3.1 文件夹里可以有文件
3.2 文件夹里可以有文件夹
Component抽象类
这是文件 文件夹的抽象定义类--组件类
/**
* 这里是文件/文件夹 的统一抽象类 "组件类"
*/
public abstract class Component {
/**
* parent是父组件的指针, 也就是本例子中的"父级目录"
*/
protected Component parent;
/**
* 获取组件名字
*/
public abstract String getName();
/**
* 获取组件的大小
*/
public abstract int getSize();
/**
* @implNote 向一个组件中添加一个组件.
* @implSpec 组合模式中的叶子(Leaf)节点不可以使用这个方法, 用例子说明的话:
* 一个文件不可以添加一个文件或文件夹, 只能是文件夹来添加文件或文件夹
*/
public Component add(Component entry) {
throw new RuntimeException("不支持此类操作...");
}
public void printList() {
printList("");
}
protected abstract void printList(String prefix);
/**
* 获取绝对路径
*/
public String getFullName() {
StringBuilder fullName = new StringBuilder();
Component entry = this;
do {
fullName.insert(0, "/" + entry.getName());
entry = entry.parent;
} while (entry != null);
return fullName.toString();
}
/**
* 其中的getSize() 会被子类重写, 因为文件大小可以很容易知道, 但是文件夹大小需要递归计算
*/
@Override
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
MyFile类
MyFile类在组合模式中是Leaf(叶子)节点, 因为他不会包含其他组件.
我们可以把组件模式想象成一颗树(绿色表示文件夹, 橙色表示文件):
组合模式的叶子节点指的是文件节点, 因为文件永远也不会有子节点.
而绿的c1节点虽然现在是叶子节点, 但是如果给c1文件夹添加了一个文件的话, 他就不再是叶子节点了.
组合模式里无法继续添加子节点的就被称作Leaf(叶子)
上面那个树, 等价于下图:
public class MyFile extends Component {
private String name;
private int size;
public MyFile(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getSize() {
return this.size;
}
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this.toString());
}
}
MyDirectory类
MyDirectory类作为一个文件夹, 可以包含其他组件(文件/文件夹)
public class MyDirectory extends Component {
private String name;
private ArrayList<Component> substance = new ArrayList<>();
public MyDirectory(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
/**
* 获取文件夹的大小:
* 1. size一开始是0
* 2. 如果是文件, 那么直接把文件的大小加到size上
* 3. 如果是文件夹, 那么就递归地查出该文件夹的大小, 然后加到size上.
*/
@Override
public int getSize() {
int size = 0;
for (Component entry : substance) {
size += entry.getSize();
}
return size;
}
/**
* 像一个文件夹中添加一个文件或文件夹
*/
@Override
public Component add(Component entry) {
substance.add(entry);
entry.parent = this;
return this;
}
/**
* 1. 打印当前目录的路径
* 2. 然后打印出子目录, 子文件的路径
* 3. 子目录再打印子子目录/子子文件的路径...如此递归
*/
@Override
protected void printList(String prefix) {
System.out.println(prefix + "/" + this.toString());
for (Component entry : substance) {
entry.printList(prefix + "/" + name);
}
}
}
Main
用于测试运行
public class Main {
public static void main(String[] args) {
/* 本例子目录结构如下:
一级目录 二级目录 三级目录
a1
|-------b1
|-------b2
|
a2
|-------c1
|-------Composite.java
|-------king.python
|-------c2
*/
MyDirectory a1 = new MyDirectory("a1");
MyDirectory a2 = new MyDirectory("a2");
MyDirectory b1 = new MyDirectory("b1");
MyDirectory b2 = new MyDirectory("b2");
a1.add(b1).add(b2);
MyDirectory c1 = new MyDirectory("c1");
MyDirectory c2 = new MyDirectory("c2");
a2.add(c1).add(c2);
MyFile java = new MyFile("Composite.java", 100);
MyFile python = new MyFile("king.python", 214);
c1.add(java).add(python);
/*-****** getFullName() ************-*/
System.out.println(java.getFullName());
System.out.println(python.getFullName());
System.out.println("");
/*-****** getFullName() ************-*/
a1.printList();
System.out.println("-----");
a2.printList();
}
}
---------------------------------------------------------
学如不及,犹恐失之