【趣味设计模式系列】之【组合模式】
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 缺点
组合模式不容易限制组合中的构件。