1. 簡介
組合模式(Composite Pattern):將物件組合成樹形結構以表示部分-整體的層次關係。
2. 示例
假設要設計一個檔案系統的目錄,需要靈活的在某個目錄下新增、刪除目錄或檔案,統計指定目錄下的檔案個數,計算指定目錄下的檔案大小。
設計類圖如下:
抽象類Node
package com.wzj.composite;
/**
* @Author: wzj
* @Date: 2020/9/23 15:33
* @Desc:
*/
public abstract class Node {
//檔案路徑
protected String path;
public Node(String path) {
this.path = path;
}
public String getPath() {
return path;
}
// 統計目錄下檔案數目
public abstract int countNumOfFiles();
// 統計目錄下檔案大小
public abstract long countSizeOfFiles();
// 列印路徑
public abstract void print();
}
檔案類FileNode
package com.wzj.composite;
import java.io.File;
/**
* @Author: wzj
* @Date: 2020/9/23 16:38
* @Desc:
*/
public class FileNode extends Node {
public FileNode(String path) {
super(path);
}
@Override
public String getPath() {
return super.getPath();
}
@Override
public int countNumOfFiles() {
return 1;
}
@Override
public long countSizeOfFiles() {
File file = new File(path);
if (!file.exists()) return 0;
return file.length();
}
@Override
public void print() {
System.out.println(path);
}
}
目錄類DirectorNode
package com.wzj.composite;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: wzj
* @Date: 2020/9/23 16:48
* @Desc:
*/
public class DirectoryNode extends Node{
public List<Node> list = new ArrayList<>();
public DirectoryNode(String path) {
super(path);
}
@Override
public String getPath() {
return super.getPath();
}
@Override
public int countNumOfFiles() {
int num = 0;
for (Node node : list) {
num += node.countNumOfFiles();
}
return num;
}
@Override
public long countSizeOfFiles() {
long size = 0;
for (Node node : list) {
size += node.countSizeOfFiles();
}
return size;
}
@Override
public void print() {
System.out.println(path);
}
public void addSubNode(Node node) {
list.add(node);
}
public void removeSubNode(Node node) {
int size = list.size();
int i = 0;
for (; i < size; ++i) {
if (list.get(i).getPath().equalsIgnoreCase(node.getPath())) {
break;
}
}
if (i < size) {
list.remove(i);
}
}
}
客戶端Client
package com.wzj.composite;
/**
* @Author: wzj
* @Date: 2020/9/23 20:44
* @Desc:
*/
public class Client {
public static void main(String[] args) {
DirectoryNode root = new DirectoryNode("root");
DirectoryNode chapter1 = new DirectoryNode("chapter1");
DirectoryNode chapter2 = new DirectoryNode("chapter2");
Node r1 = new FileNode("r1.txt");
Node c11 = new FileNode("c11.txt");
Node c12 = new FileNode("c12.txt");
DirectoryNode b21 = new DirectoryNode("section21");
Node c211 = new FileNode("c211.txt");
Node c212 = new FileNode("c212.txt");
root.addSubNode(chapter1);
root.addSubNode(chapter2);
root.addSubNode(r1);
chapter1.addSubNode(c11);
chapter1.addSubNode(c12);
chapter2.addSubNode(b21);
b21.addSubNode(c211);
b21.addSubNode(c212);
printTree(root, 0);
System.out.println("root files num:" + root.countNumOfFiles());
System.out.println("/root/chapter1/ files num:" + chapter2.countNumOfFiles());
}
// 列印樹狀結構
public static void printTree(Node root, int depth) {
for (int i = 0; i < depth; i++) {
System.out.print("--");
}
root.print();
if(root instanceof DirectoryNode) {
for (Node n : ((DirectoryNode)root).list) {
printTree(n, depth + 1);
}
}
}
}
結果
root
--chapter1
----c11.txt
----c12.txt
--chapter2
----section21
------c211.txt
------c212.txt
--r1.txt
root files num:5
/root/chapter1/ files num:2
3. 原始碼分析
SpringMVC中對引數的解析使用的是HandlerMethodArgumentResolver
介面,該類有一個實現類為HandlerMethodArgumentResolverComposite
,為組合類,又持有其他HandlerMethodArgumentResolver
物件,在它的實現方法中是對其他組合模式中的節點進行迴圈處理,從而選擇最適合的一個。
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
// 對引數解析器的引用
private final List<HandlerMethodArgumentResolver> argumentResolvers =
new LinkedList<HandlerMethodArgumentResolver>();
// 對其所擁有的物件迴圈,找到最適合的引數解析器
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
parameter.getGenericParameterType() + "]");
}
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
4. 總結
4.1 優點
- 高層呼叫簡單
一棵樹形機構中的所有節點都是Component,區域性和整體對呼叫者來說沒有任何區別,也就是說,高層模組不必關心自己處理的是單個物件還是整個組合結構,簡化了高層模組的程式碼。 - 節點自由增加
使用了組合模式後,我們可以看看,如果想增加一個樹枝節點、樹葉節點是不是都很容易,只要找到它的父節點就成,非常容易擴充套件,符合開閉原則,對以後的維護非常有利。
4.2 缺點
組合模式不容易限制組合中的構件。