123设计模式(Design Pattern)是软件开发中经过验证的最佳实践,用于解决在软件设计中常见的问题。
经典设计模式可分为三种:
| 类型 | 关注点 |
|---|---|
| 创建型 | 对象怎么来 |
| 结构型(本章节) | 对象怎么组装 |
| 行为型 | 对象间怎么协作 |
适配器
将一个类的接口转换为可不希望的另一个接口
众所周知,不同国家的电源插座是不一样的,通常出国旅游都会带上这么一个万能适配器

适配器本身就是在不改原来的代码基础上,就在中间加一层”转接头”,通过”转接头”实现功能的调用。
在实现方式上分为两种,类适配器以及对象适配器先看两者的类图

类适配器的主要思路是适配器继承被适配者并实现目标接口,结构上比较简单,但部分只能单基础的语言(如Java 需接口+实现类),耦合的比较死。
所以更常用的是下面这种对象适配器,看图:

对象适配器,在适配器中持有一个被适配者实例的方式实现,无需多重继承
桥接
将抽象和实现分离,使其可以独立变化
先说明一点,桥接模式定义中的抽象和实现,并不是抽象类和实现类的意思
举个例子,一家工厂,要为不同型号的手机,生产不同的手机壳
| 维度 | 变化 |
|---|---|
| 型号 | iPhone 17 / 18,Pro / Pro Max |
| 材质 | 硅胶 / PC / 碳纤维 |
而桥接模式中会遇到的四种角色,分别对应如下:
| 角色名 | 管什么 | 对应 |
|---|---|---|
| Abstraction 抽象化角色 | 高层业务概念 | 手机型号 |
| RefinedAbstraction 扩展抽象 | 抽象那一侧的具体化 | iPhone 18 Pro Max |
| Implementor 实现化角色 | 底层能力的接口 | 壳材质 |
| ConcreteImplementor 具体实现 | 实现那一侧的具体化 | 硅胶壳 |
如果不用桥接模式,我们需要为不同的手机,每种材质的手机壳,单独定义一个类, 这时候突然想要为每种手机壳添加磁吸功能,那每个类都需要修改,岂不是要爆炸
桥接模式主要就在:将手机(抽象)和壳(实现)分开定义,然后再用继承或是其他东西,将两个部分链接起来,先看图:

在Abstraction中持有Implementor,实现两者的联系
用一小部分代码举例,会比较直观些:
/** Implementor:实现化角色 —— 只定义"材质能提供什么能力" */
public interface Material {
String getMaterial(); // 对应类图里的 + getMaterial()
default String getTexture() { // 对应类图里的 + xxxxx()
return "哑光";
}
}
/** ConcreteImplementor1:具体实现 —— 硅胶(只管材质,不管型号) */
public class Silicone implements Material {
@Override
public String getMaterial() {
return "硅胶";
}
@Override
public String getTexture() {
return "亲肤防滑";
}
}
// ..........
/**
* Abstraction:抽象化角色
* 关键:用组合持有 Implementor —— 这就是那座"桥"
*/
public abstract class PhoneCase {
protected Material implementor; // ← 对应类图连线上的角色名 implementor
protected PhoneCase(Material implementor) {
this.implementor = implementor;
}
/** 对应类图 + assemble(),抽象方法,交给子类实现 */
public abstract void assemble();
}
/** RefinedAbstraction1:扩展抽象 —— iPhone 17 */
public class iPhone17Case extends PhoneCase {
public iPhone17Case(Material implementor) {
super(implementor);
}
@Override
public void assemble() {
System.out.println("【iPhone 17 壳】尺寸 6.1 英寸,单摄开孔");
System.out.println(" 材质:" + implementor.getMaterial());
System.out.println(" 手感:" + implementor.getTexture());
System.out.println(" → 组装完成,售价 ¥" + price());
}
private int price() {
return implementor.getMaterial().equals("硅胶") ? 99 : 129;
}
}
// ..........
public class Client {
public static void main(String[] args) {
// 运行时自由组合:2 种型号 × 2 种材质 = 4 种产品
PhoneCase c1 = new iPhone17Case(new Silicone());
PhoneCase c2 = new iPhone18Case(new PcHardShell());
c1.assemble();
System.out.println("-----");
c2.assemble();
System.out.println("-----");
// 同一型号换材质:不需要新建类,也不用改 iPhone18Case 一行代码
PhoneCase c3 = new iPhone18Case(new Silicone());
c3.assemble();
}
}
组合
将对象组合成树形结构,以表示/整体;客户端无需关注它容器还是节点,直接递归调用
你在电脑上选中一个东西,右键 → 查看属性:
- 如果选的是文件,显示 3.2 MB。
- 如果选的是文件夹,显示 1.8 GB (目录下递归所有文件体积之和)。
对于用户来说,无需关注是文件夹还是单一文件,如果是文件夹,系统会自己进去将文件大小累加返回,而文件就直接返回其大小。
在组合模式中,叶子和容器实现同一个接口,容器负责”往下传”,叶子负责”直接答”
先看图:

用代码举个例子:
# ==================== Component(抽象构件)====================
class FileSystemNode(ABC):
@abstractmethod
def size(self) -> int: ...
@abstractmethod
def print_tree(self, indent: int = 0): ...
# ==================== Leaf(叶子构件)====================
class File(FileSystemNode):
def __init__(self, name: str, size_bytes: int):
self.name = name
self.size_bytes = size_bytes
def size(self) -> int:
return self.size_bytes # 叶子:直接给出答案
def print_tree(self, indent: int = 0):
print(" " * indent + f"📄 {self.name} ({self.size_bytes} B)")
# ==================== Composite(容器构件)====================
class Folder(FileSystemNode):
def __init__(self, name: str):
self.name = name
self._children: list[FileSystemNode] = [] # ← 核心:装的是"同一种接口"
def add(self, node: FileSystemNode):
self._children.append(node)
def remove(self, node: FileSystemNode):
self._children.remove(node)
# 容器的逻辑:自己不干活,把工作分发给所有孩子
def size(self) -> int:
return sum(child.size() for child in self._children) # 递归
def print_tree(self, indent: int = 0):
print(" " * indent + f"📁 {self.name}")
for child in self._children:
child.print_tree(indent + 1) # 递归
# ==================== 客户端(完全不分叶子和容器)====================
root = Folder("项目")
src = Folder("src")
doc = Folder("docs")
src.add(File("main.py", 4096))
src.add(File("utils.py", 2048))
doc.add(File("README.md", 1024))
root.add(src)
root.add(doc)
root.add(File("requirements.txt", 256))
print(f"项目总大小:{root.size()} B") # → 7424
root.print_tree()
装饰
动态的为一个对象添加一些额外的元素,但不改它自己
举个栗子:一杯咖啡是由溶缩咖啡液+各种配料组成,一杯咖啡可能会添加一种甚至多种配料
如果用继承实现,3 种料就要 2³ = 8 个类,后续可添加的配料越多,需要实现的类就越多
有没有一种方法只需要在咖啡液的基础上叠加实现各种咖啡,这就是装饰模式
在原有的咖啡液的基础上,叠加一层层的壳,实现不修改咖啡液本身的情况下,附加额外的元素,先看图:

| 角色 | 本例 | 职责 |
|---|---|---|
| Component | Coffee | 公共接口,被装饰者和装饰器都必须实现它 |
| ConcreteComponent | Espresso | 原始对象,可以被装饰,但自己不装饰别人 |
| Decorator | CondimentDecorator | 持有一个 Component,默认把所有方法转发给它(空转) |
| ConcreteDecorator | Milk / CoconutMilk / Whip | 重写需要增强的方法,转发前后加点东西 |
# 实现一杯咖啡,就变成在原有咖啡对象上,不同叠加的过程
drink = Whip(Milk(Espresso()))
外观
为子系统中的一组接口,提供一个一致的界面
家里有米家这类智能家居的应该用过这个功能
定义一个快捷方式,例如看电影,在自动化中定义(打开投影仪、关闭窗帘、打开音响、设置音量为50%…)
一系列负责的功能接口,被封装到一个快捷指令,这就是外观模式的核心思想

(图片来源见图右下角)
享元
通过共享对象来减少创建大量相似的对象
享元(共享元素)是一种用于内存优化的设计模式
当大量对象长得差不多时,就把”相同的部分”抽出来只存一份,“不同的部分”当成参数传进去
举一个围棋的例子,一盘棋有 361 个交叉点,理论上最多要 361 个棋子对象。但棋子只有两种本质不同的东西:黑子和白子。
- 黑子:颜色、材质、贴图、半径 —— 所有黑子完全一样。
- 白子:同上。
- 位置:每个子都不一样。
享元的做法:黑子只 new 一次,白子只 new 一次,剩下的 359 个位置,用”棋子 + 坐标”来表示。
代理
为其他对象提供一个代理,以控制该对象的访问
你想找明星谈合作,得先联系经纪人。经纪人会做三件事:
- 资格审查(你有没有预算、是不是黑名单客户)——不合格直接打回,明星根本不知道这事发生过。
- 挡掉重复请求(同一个品牌上周刚谈过)——直接回复上次的报价,不用惊动明星。
- 真的有必要时才把日程转给明星本人。
明星本人完全不知道自己被代理了,他的档期、报价、合同一个字没改。这就是代理:控制访问,而不是增强能力(这是它和装饰器的本质区别)。

参考资料:
《软件设计师教程》 - 清华大学出版社
《您的设计模式》 - 设计模式公司
一些AI给出的建议