一、定义
组合模式(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
类的适用性就很强了,毕竟万物皆对象嘛。
查看更多:设计模式分类以及六大设计原则