)
一、组合模式定义Composite 模式是一种结构型模式。它通过递归手段来构造树形的对象结构使得用户对单个对象叶子和组合对象容器的使用具有一致性——客户端无需关心当前操作的是文件还是文件夹只管调用统一接口即可访问整棵树。一句话当你要处理的对象天然是树形层级文件系统、菜单、组织架构、UI 控件树时用组合模式让叶子和枝干共用一套接口客户端统一对待。二、为什么需要组合模式动机假设没有组合模式客户端遍历目录树时只能到处写if (node instanceof Folder)判断是否是容器再决定是否递归——代码和具体类型紧耦合新增一种节点类型就要改遍所有遍历逻辑。组合模式的解决思路让叶子节点和容器节点都实现同一个抽象接口容器内部维护子节点列表。客户端拿到一个节点永远只调统一方法如display()、getChild()至于它是文件还是文件夹由对象自己决定行为。树形结构的递归遍历因此变得极其干净。三、组合模式的结构角色与职责角色职责抽象构件Component树形结构的节点抽象。为所有对象定义统一接口如display并声明管理子节点的方法add/remove/getChild可选提供管理父节点的方法叶子节点LeafComponent 的实现子类代表树的最末端没有子节点如File容器节点CompositeComponent 的实现子类代表有子节点的枝干内部维护子节点集合并递归处理如Folderchildren«interface»IFiledisplay()add(IFile) : : booleanremove(IFile) : : booleangetChild() : : ListIFileFile-name: StringFolder-name: String-children: ListIFile四、代码实现透明式文件系统目录树示例本节是透明式写法Component 声明全部方法叶子默认空实现也是最常用的写法。4.1 抽象构件IFileimportjava.util.List;/** * 文件节点抽象是文件和目录的公共父类 */publicinterfaceIFile{voiddisplay();// 显示自身名称booleanadd(IFilefile);// 添加子节点booleanremove(IFilefile);// 移除子节点ListIFilegetChild();// 返回子节点列表}4.2 叶子节点Fileimportjava.util.List;publicclassFileimplementsIFile{privateStringname;publicFile(Stringname){this.namename;}Overridepublicvoiddisplay(){System.out.println(name);}// 叶子没有子节点默认返回 false透明式的典型处理见第五节取舍Overridepublicbooleanadd(IFilefile){returnfalse;}Overridepublicbooleanremove(IFilefile){returnfalse;}OverridepublicListIFilegetChild(){returnnull;}}4.3 容器节点Folderimportjava.util.ArrayList;importjava.util.List;publicclassFolderimplementsIFile{privateStringname;privateListIFilechildren;publicFolder(Stringname){this.namename;childrennewArrayList();}Overridepublicvoiddisplay(){System.out.println(name);}Overridepublicbooleanadd(IFilefile){returnchildren.add(file);}Overridepublicbooleanremove(IFilefile){returnchildren.remove(file);}OverridepublicListIFilegetChild(){returnchildren;}}4.4 客户端递归遍历整棵树importjava.util.List;publicclassMainClass{publicstaticvoidmain(String[]args){FolderrootnewFolder(C:);FolderstudynewFolder(学习资料);FileresumenewFile(简历.pdf);FolderphotosnewFolder(照片);FiletravelnewFile(旅游.jpg);root.add(study);root.add(resume);study.add(photos);photos.add(travel);// 从根统一遍历客户端完全不关心节点类型displayTree(root,0);}// 统一接口遍历叶子 getChild() 返回 null自动终止递归publicstaticvoiddisplayTree(IFilenode,intdepth){for(inti0;idepth;i){System.out.print( );}node.display();ListIFilechildrennode.getChild();if(children!null){for(IFilechild:children){displayTree(child,depth1);}}}}运行结果缩进即层级C: 学习资料 照片 旅游.jpg 简历.pdf关键点客户端遍历时没有写任何instanceof判断——正是因为File.getChild()返回null、统一接口让叶子也能假装有子节点列表递归才能用同一套逻辑对待所有节点。这正是组合模式的价值所在。五、面试必考透明式 vs 安全式含完整代码上面的写法里Component声明了add/remove/getChild而Leaf的这几个方法只能返回false/null。这就是组合模式最核心的取舍方案做法优点缺点透明式第四节在Component中声明全部方法叶子默认空实现/抛异常客户端可以完全统一地用Component处理所有节点无需类型判断叶子假装能 add 但无效类型不安全易误调用安全式本节只在Composite中声明add/removeComponent不声明叶子不会出现无意义的 add 方法编译期就拦住误用客户端必须区分叶子/容器要用instanceof或强转失去统一性5.1 安全式代码importjava.util.ArrayList;importjava.util.List;// 安全式Component 只声明公共行为不声明增删interfaceIFileSafe{voiddisplay();ListIFileSafegetChild();}// 叶子根本没有 add/remove 方法classFileSafeimplementsIFileSafe{privateStringname;publicFileSafe(Stringname){this.namename;}Overridepublicvoiddisplay(){System.out.println(name);}OverridepublicListIFileSafegetChild(){returnnull;}}// 容器增删只在 Composite 自己身上声明classFolderSafeimplementsIFileSafe{privateStringname;privateListIFileSafechildrennewArrayList();publicFolderSafe(Stringname){this.namename;}Overridepublicvoiddisplay(){System.out.println(name);}OverridepublicListIFileSafegetChild(){returnchildren;}// 独有方法只有持有 FolderSafe 引用才能调用publicbooleanadd(IFileSafefile){returnchildren.add(file);}publicbooleanremove(IFileSafefile){returnchildren.remove(file);}}客户端差异FolderSaferootnewFolderSafe(C:);root.add(newFileSafe(a.txt));// ✅ 持有 FolderSafe可 add// IFileSafe node root;// node.add(...); // ❌ 编译报错Component 没有 add —— 这就是安全5.2 折中方案推荐实际项目多数选透明式换调用统一但为避免叶子误调 add 静默失败可让叶子的方法直接抛异常而非默默返回falseOverridepublicbooleanadd(IFilefile){thrownewUnsupportedOperationException(叶子节点不支持 add);}这样既保留了透明式的统一调用又能在误用时立刻报错兼顾透明与安全。六、应用场景对象结构呈树形层级文件系统、菜单/导航树、公司组织架构、XML/JSON 节点、GUI 容器控件树如 Swing 的Container。希望客户端忽略叶子与容器的差异统一地增删、遍历、执行操作。树形结构可能频繁变动动态增删节点组合模式让增删与遍历解耦。七、真实世界中的组合模式JDK / 框架组合模式不是纸上谈兵JDK 和主流框架里随处可见java.awt.Component/java.awt.ContainerSwing 里Container继承Component并持有子Component列表是最经典的 GOF 组合实现。org.w3c.dom.NodeXML DOM 树中元素节点和文本节点都实现Node可统一遍历。javax.swing/ 各类 UI 控件树窗口→面板→按钮的层级就是组合结构。主流框架的配置/路由树如 Spring Cloud 的复合配置、规则引擎的 AST底层也是组合思想的变体。下次看到一个对象既能是单个元素、又能是一堆元素的容器的设计基本就是组合模式。八、优缺点优点客户端一致对待叶子与容器调用方代码简洁无instanceof满天飞。符合开闭原则新增节点类型只需实现Component遍历逻辑不动。递归自然契合树形结构动态增删节点非常方便。缺点设计较抽象难限制某容器只能放特定类型子节点编译期无法约束。透明式下叶子拥有了不该有的方法易误用安全式又牺牲统一性。容器内部常用ArrayList等非线程安全集合多线程并发增删需外部同步树过深时递归遍历还有栈溢出风险。九、总结组合模式 用同一接口统一叶子和容器通过递归构建并遍历树形对象结构属于结构型模式。三角色抽象构件Component、叶子Leaf、容器Composite。最核心取舍是透明式 vs 安全式要统一调用选透明式叶子抛异常更稳要类型安全选安全式或叶子抛异常折中。选型口诀对象是一棵树、且想统一操作节点时用组合模式。一句话总结当你的数据天然成树别让客户端满屏instanceof区分文件还是文件夹——交给组合模式一视同仁递归到底。