一、定义
组合模式(Composite Pattern)也叫合成模式,有时又叫做部分-整体模式(Part-Whole),主要是用来描述部分与整体的关系,其定义如下:
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
二、角色
Component
是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。Leaf
在组合中表示叶子节点对象,叶子结点没有子节点。Composite
定义树枝节点行为,用来存储子节点,在 Component 接口中实现与子部件有关操作,如增加(add)和删除(remove)等。
三、使用场景
- 维护和展示部分-整体关系的场景,如树形菜单、文件和文件夹管理。
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
四、实例分析
组合模式非常适合“树”形结构,例如文件夹系统就是一个典型的“树”行结构,文件夹就是 Composite
,子文件即 Leaf
,它们都属于文件(Component
)。这里,我们可以定义一个 TreeNode
类模拟“树”形结构,它同时饰演 Component
、Leaf
、Composite
三个角色。
1. TreeNode
类
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class TreeNode {
public static final String NODE_SEPARATOR = ":";
private int mId;
private int mLastId;
private TreeNode mParent;
private final List<TreeNode> children;
private Object mValue;
public static TreeNode root() {
TreeNode root = new TreeNode(null);
return root;
}
private int generateId() {
return ++mLastId;
}
public TreeNode(Object value) {
children = new ArrayList<>();
mValue = value;
}
public TreeNode addChild(TreeNode childNode) {
childNode.mParent = this;
childNode.mId = generateId();
children.add(childNode);
return this;
}
public TreeNode addChildren(TreeNode... nodes) {
for (TreeNode n : nodes) {
addChild(n);
}
return this;
}
public TreeNode addChildren(Collection<TreeNode> nodes) {
for (TreeNode n : nodes) {
addChild(n);
}
return this;
}
public int deleteChild(TreeNode child) {
for (int i = 0; i < children.size(); i++) {
if (child.mId == children.get(i).mId) {
children.remove(i);
return i;
}
}
return -1;
}
public List<TreeNode> getChildren() {
return Collections.unmodifiableList(children);
}
public int size() {
return children.size();
}
public TreeNode getParent() {
return mParent;
}
public int getId() {
return mId;
}
public boolean isLeaf() {
return size() == 0;
}
public Object getValue() {
return mValue;
}
public String getPath() {
final StringBuilder path = new StringBuilder();
TreeNode node = this;
while (node.mParent != null) {
path.append(node.getId());
node = node.mParent;
if (node.mParent != null) {
path.append(NODE_SEPARATOR);
}
}
return path.toString();
}
public int getLevel() {
int level = 0;
TreeNode root = this;
while (root.mParent != null) {
root = root.mParent;
level++;
}
return level;
}
public boolean isRoot() {
return mParent == null;
}
public TreeNode getRoot() {
TreeNode root = this;
while (root.mParent != null) {
root = root.mParent;
}
return root;
}
}
2. 模拟一个公司结构,定义一个 Staff
类
public class Staff {
private String name; // 姓名
private String position; // 职位
public Staff(String name, String position) {
this.name = name;
this.position = position;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
@Override
public String toString() {
return "Staff [name=" + name + ", position=" + position + "]";
}
}
3. 模拟一个场景
public class Main {
public static void main(String[] args) {
TreeNode ceo = new TreeNode(new Staff("小马哥", "CEO"));
TreeNode developBoss = new TreeNode(new Staff("张三", "开发老大"));
TreeNode financeBoss = new TreeNode(new Staff("李四", "财务总管"));
TreeNode coderA = new TreeNode(new Staff("路人甲", "码农"));
TreeNode coderB = new TreeNode(new Staff("路人乙", "码农"));
TreeNode financeA = new TreeNode(new Staff("路人丙", "会计"));
// 他们的关系是
ceo.addChild(developBoss);
ceo.addChild(financeBoss);
developBoss.addChild(coderA);
developBoss.addChild(coderB);
financeBoss.addChild(financeA);
// 我们可以轻松获得任一一个节点的上司以及他的小弟
Staff parent = (Staff) developBoss.getParent().getValue();
System.out.println("developBoss 的老大是:" + parent.toString());
List<TreeNode> chilren = developBoss.getChildren();
for (TreeNode treeNode : chilren) {
Staff child = (Staff) treeNode.getValue();
System.out.println("developBoss 的小弟是:" + child.toString());
}
// 随时来一个新人,都可以把他加到“树”中的某个位置
TreeNode salesBoss = new TreeNode(new Staff("王二", "销售经理"));
salesBoss.addChild(new TreeNode(new Staff("路人丁", "推销员")));
ceo.addChild(salesBoss);
}
}
4. 运行结果
developBoss 的老大是:Staff [name=小马哥, position=CEO]
developBoss 的小弟是:Staff [name=路人甲, position=码农]
developBoss 的小弟是:Staff [name=路人乙, position=码农]
5. 总结分析
上例中的TreeNode
类模拟了一个类似于“树”的结构,它既可以是一个中间节点(树枝),也可以是一个子节点(叶子),取决于它的 children 的大小是不是为 0。同时,它持有一个 Object 的引用,这个 Object 可以是任一一个类的实例,这样
TreeNode
类的适用性就很强了,毕竟万物皆对象嘛。
查看更多:设计模式分类以及六大设计原则