一、结构型模式
在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。
结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。
在 Java 开发中,结构型模式主要包含以下 7 种经典实现:
二、组合模式
2.1 介绍
组合模式是一种结构型设计模式,它将单个对象和对象组合以树形结构表示,让用户可以用一致的方式处理单个对象和组合对象,无需区分是叶子还是容器。
2.2 角色
-
抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
-
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
-
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
三、分类
3.1 透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 add、remove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
3.2 安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
四、透明组合模式代码实现
本文采用中文命名类名来方便理解
4.1 抽象根节点
public abstract class 文件组件 {
public abstract void add(文件组件 component);
public abstract void remove(文件组件 component);
public abstract 文件组件 getChild(int index);
}
4.2 树枝节点
public class 文件夹 extends 文件组件{
private ArrayList<文件组件> components = new ArrayList<>();
@Override
public void add(文件组件 component) {
components.add(component);
}
@Override
public void remove(文件组件 component) {
components.remove(component);
}
@Override
public 文件组件 getChild(int index) {
return components.get(index);
}
}
4.3 叶子节点
public class 文件 extends 文件组件{
@Override
public void add(文件组件 component) {
throw new UnsupportedOperationException("不支持此操作");
}
@Override
public void remove(文件组件 component) {
throw new UnsupportedOperationException("不支持此操作");
}
@Override
public 文件组件 getChild(int index) {
throw new UnsupportedOperationException("不支持此操作");
}
}
4.4 客户端
public static void main(String[] args) {
文件组件 floder = new 文件夹();
文件组件 file1 = new 文件();
文件组件 file2 = new 文件();
floder.add(file1);
floder.add(file2);
floder.remove(file1);
floder.remove(file2);
}
五、安全组合模式代码实现
5.1 抽象根节点
public abstract class 文件组件 {
String name;
public abstract void display();
}
5.2 树枝节点
public class 文件夹 extends 文件组件{
private ArrayList<文件组件> components = new ArrayList<>();
public 文件夹(String name) {
this.name = name;
}
public void add(文件组件 component) {
components.add(component);
}
public void remove(文件组件 component) {
components.remove(component);
}
public 文件组件 getChild(int index) {
return components.get(index);
}
@Override
public void display() {
System.out.println("文件夹:" + name);
for (文件组件 component : components) {
component.display();
}
}
}
5.3 叶子节点
public class 文件 extends 文件组件{
public 文件(String name) {
this.name = name;
}
@Override
public void display() {
System.out.println("文件:" + name);
}
}
5.4 客户端
public class 客户端 {
public static void main(String[] args) {
文件夹 root = new 文件夹("根目录");
文件组件 file1 = new 文件("文件一");
文件组件 file2 = new 文件("文件二");
文件夹 floder = new 文件夹("文件夹");
root.add(file1);
root.add(floder);
floder.add(file2);
root.display();
floder.remove(file2);
}
}
文件夹:根目录
文件:文件一
文件夹:文件夹
文件:文件二
Process finished with exit code 0
六、优缺点
6.1 优点
-
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
-
客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
-
在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
-
组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
6.2 缺点
- 透明式组合模式:违反类型安全,存在运行时风险
- 安全式组合模式:客户端需区分节点类型,丧失部分透明性
- 若树形结构复杂(比如节点有多种类型、有复杂的层级规则),抽象根节点的设计会很考验功底,容易过度设计。
- 对于简单的层级结构(比如只有两层),用组合模式会显得 “杀鸡用牛刀”,增加不必要的复杂度。
七、对比
7.1 与装饰者模式对比
Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
这两种模式都基于 “组合” 思想,但目标和应用场景完全不同
| 核心目标 | 构建树形结构,统一处理 “整体 – 部分”(比如文件夹 + 文件) | 动态给对象添加职责 / 功能(比如给咖啡加奶、加糖) |
| 结构关系 | 节点之间是 “包含” 关系(文件夹包含文件 / 子文件夹) | 装饰器和被装饰者是 “包装” 关系(一层套一层) |
| 对象层级 | 横向的树形层级(父子节点) | 纵向的嵌套层级(装饰器链) |
| 客户端感知 | 客户端把单个 / 组合对象视为同一类型(都调用display()) | 客户端把装饰后的对象视为原对象的增强版(还是同一个抽象类型) |
| 典型例子 | 文件系统、菜单树、组织架构 | 咖啡加配料、IO 流包装、日志增强 |
7.2 对比适配器模式
Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
两者都涉及 “统一接口”,但目标和场景完全不同
| 核心目标 | 统一 “整体 – 部分” 的接口,方便树形操作 | 把不兼容的接口转换成客户端能使用的接口 |
| 对象关系 | 同一抽象体系下的对象组合(都是FileComponent) | 不同抽象体系的对象适配(比如把USB转Type-C) |
| 适用场景 | 处理层级结构 | 处理接口兼容问题 |
八、源码举例 AWT 的组件体系
8.1 抽象根节点
public abstract class Component implements ImageObserver, MenuContainer,
Serializable
{
…
public void paint(Graphics g) {
}
…
}
8.2 树枝节点
public class Container extends Component {
…
private java.util.List<Component> component = new ArrayList<>();
…
public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}
…
public void remove(Component comp) {
synchronized (getTreeLock()) {
if (comp.parent == this) {
int index = component.indexOf(comp);
if (index >= 0) {
remove(index);
}
}
}
}
…
public void paint(Graphics g) {
if (isShowing()) {
synchronized (getObjectLock()) {
if (printing) {
if (printingThreads.contains(Thread.currentThread())) {
return;
}
}
}
// The container is showing on screen and
// this paint() is not called from print().
// Paint self and forward the paint to lightweight subcomponents.
// super.paint(); — Don't bother, since it's a NOP.
GraphicsCallback.PaintCallback.getInstance().
runComponents(getComponentsSync(), g, GraphicsCallback.LIGHTWEIGHTS);
}
}
…
}
8.3 叶子节点
public class Button extends Component implements Accessible {
…
}
九、其他相关设计模式
9.1 单例模式
Java 设计模式・单例模式篇:从思想到代码实现-CSDN博客
9.2 简单工厂+工厂方法+抽象工厂模式
Java 设计模式・工厂模式篇:从思想到代码实现-CSDN博客
9.3 建造者模式
Java 设计模式・建造者模式篇:从思想到代码实现-CSDN博客
9.4 原型模式
Java 设计模式・原型模式篇:从思想到代码实现-CSDN博客
9.5 代理模式
Java 设计模式・代理模式篇:从思想到代码实现-CSDN博客
9.6 适配器模式
Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
9.7 装饰器模式
Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
9.8 桥接模式
Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
9.9 外观模式
Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
9.10 组合模式
本篇
9.11 享元模式
Java 设计模式・享元模式篇:从思想到代码实现-CSDN博客





