组合模式在我们的生活场景中也有体现,比如说我们的公司组织结构,大的部门中间包含一个个一级部门,一级部门下还有不同的子部门。 或者说我们计算机中的文件系统,根目录下有各文件,文件夹下可以有子文件夹,也可以有文件。子文件夹下也可以有文件夹或者文件。类似于这种树形结构的业务架构,今天所说的组合模式能可好的解决此类问题。
组合模式的定义与结构
组合(Composite Pattern)模式的定义:将一组对象组织(Compose)成树形结构,以表示一种“部分-整体”的层次结构。使用户对单个对象和组合对象具有一致的访问性。
- 模式的结构 组合模式包含以下主要角色。
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含操作树枝构建的操作等方法。
组合模式结构图:
代码案例: 通过模拟文件系统,体会一下组合模式的使用;
- 抽象构件 这里可以使用接口,或抽象类。
public abstract class FileSystemComponent {
protected String path;
public FileSystemComponent(String path) {
this.path = path;
}
// 文件数量
public abstract int countNumOfFiles();
// 文件内容大小
public abstract Long countSizeOfFiles();
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public abstract String show();
}
- 树叶构件 文件类(在文件系统中,文件是最小单元)
public class FileLeaf extends FileSystemComponent{
public FileLeaf(String path) {
super(path);
}
@Override
public int countNumOfFiles() {
return 0;
}
@Override
public Long countSizeOfFiles() {
return 0L;
}
@Override
public String show() {
return "/"+path;
}
}
- 树枝构件 文件夹(文件夹可以包含子文件夹和文件)
public class DirectoryComposite extends FileSystemComponent {
private List<FileSystemComponent> subFileStemComponent = new ArrayList<FileSystemComponent>();
public DirectoryComposite(String path) {
super(path);
}
@Override
public int countNumOfFiles() {
int numOfFiles = 0;
for (FileSystemComponent fileLeaf : subFileStemComponent) {
numOfFiles += fileLeaf.countNumOfFiles();
}
return numOfFiles;
}
@Override
public Long countSizeOfFiles() {
Long sizeOfFiles = 0L;
for (FileSystemComponent fileSystemComponent : subFileStemComponent) {
sizeOfFiles += fileSystemComponent.countSizeOfFiles();
}
return sizeOfFiles;
}
public void addSubNode(FileSystemComponent fileSystemComponent) {
subFileStemComponent.add(fileSystemComponent);
}
public void removeSubNode(FileSystemComponent fileSystemComponent) {
int size = subFileStemComponent.size();
//循环当前文件夹下的所有目录
int i =0;
for (; i < size; i++) {
// 如果文件名相同 获取当前的坐标
if (subFileStemComponent.get(i).getPath().equalsIgnoreCase(fileSystemComponent.getPath())) {
break;
}
}
//根据坐标删除文件
subFileStemComponent.remove(i);
}
@Override
public String show() {
for (Object fileSystemComponent : subFileStemComponent) {
System.out.println(path + ((FileSystemComponent)fileSystemComponent).show());
}
return "";
}
}
- client 使用方
public class Client {
public static void main(String[] args) {
// 模拟一下树形结构的管理
// |- Desktop
// |---------dir
// |-------------test.txt
// |-------------tmp
// |----------------java.word
// 文件1
FileSystemComponent text = new FileLeaf("test.txt");
// 文件夹1
DirectoryComposite dir = new DirectoryComposite("Desktop/dir");
// 文件夹 Desktop/dir 下新增一个文件
dir.addSubNode(text);
// 文件夹2 Desktop/dir 下新增一个文件夹
DirectoryComposite tmp = new DirectoryComposite("Desktop/dir/tmp");
// 文件2
FileSystemComponent test = new FileLeaf("java.word");
tmp.addSubNode(test);
// 文件夹 Desktop/dir 添加到文件夹 Desktop/dir 下
dir.addSubNode(tmp);
dir.show();
}
}
执行结果:
Desktop/dir/test.txt
Desktop/dir/tmp/java.word
Desktop/dir
案例结构图:
组合模式的优缺点
优点:
- 组合模式使得使用方代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
组合模式使用场景
其实组合模式更偏向于具体某一种场景的使用,也可以说是针对于树形这种结构的场景。所以它解决的问题也比较单一。
- 在需要表示一个对象整体与部分的层次结构的场合。
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
组合模式在源码中使用
1.组合模式在HashMap中的使用 先看一段案例代码
public class JDKUsage {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 1);
map.put("b", 2);
Map<String, Integer> map1 = new HashMap<String, Integer>();
map1.put("c", 3);
map.putAll(map1);
System.out.println(map);
}
}
执行结果:{a=1, b=2, c=3}
不用多讲,都会使用Map的Api,那它底层是如何实现的,为什么说这里使用到了组合模式。
首先看下Hash的层次结构:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
继承AbstractMap<K,V>
,实现Map<K,V>
.
这里的Map<K,V>
就是抽象构建角色,包含put、putAll
等方法,而HashMap
则为中间构件角色,所有的数据操作都是在Node
节点,静态内部类的数组 Node<K,V>[] tab
为叶子节点角色。
// node节点
static class Node<K, V> implements Map.Entry<K, V> {
final int hash;
final K key;
V value;
Node<K, V> next;
Node(int hash, K key, V value, Node<K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
//略
// 往节点set值 put、putAll最终都会调此方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K, V>[] tab;
Node<K, V> p;
int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K, V> e;
K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K, V>) p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
//略
}
从HashMap案例中,可以看到设计模式其实提供的是一种软件设计思想,并没有完全安装标准的概念来。许多优秀的框架,也是基于好的设计模式,灵活设计模块的组件。