1. 面向对象基础与 SOLID 原则
1.1 面向对象的四大特性
封装:隐藏内部状态,仅暴露必要接口,降低耦合。
继承:子类复用父类结构,表达 is-a 关系(Java 单继承、接口多实现)。
多态:同一接口在不同运行时类型上有不同行为,依赖抽象而非具体。
抽象:抽取共同特征,用抽象类/接口约束契约。
设计目标:高内聚(模块内部职责单一)、低耦合(模块之间依赖最小化)、对修改关闭对扩展开放(OCP)。
1.2 五大设计原则(SOLID)
| 原则 | 全称 | 核心含义 | 违反的典型症状 |
|---|---|---|---|
| S | Single Responsibility | 一个类只有一个变更理由 | 类方法超过 20 个、工具类泛滥 |
| O | Open/Closed | 对扩展开放,对修改关闭 | 每加功能就改老类 |
| L | Liskov Substitution | 子类可替换父类且不破坏行为 | Rectangle 继承 Square 后 setWidth 语义崩坏 |
| I | Interface Segregation | 接口按职责拆分,不强迫实现不需要的方法 | 一个巨型接口,实现类被迫写空实现 |
| D | Dependency Inversion | 依赖抽象,不依赖具体实现 | 高层模块直接 new 底层模块 |
// DIP 正确示范:依赖接口而非具体实现
public class NotificationService {
private final Sender sender; // 依赖抽象(由调用方注入)
public NotificationService(Sender sender) { this.sender = sender; }
public void send(String msg) { sender.deliver(msg); }
}
public interface Sender {
void deliver(String msg);
}
public class EmailSender implements Sender {
@Override public void deliver(String msg) { /* 发邮件 */ }
}
2. 创建型模式:单例(Singleton)
2.1 用途与变体
单例保证一个类只有一个实例并全局可访问,常用于配置中心、连接池、线程池、日志器等。
| 实现方式 | 线程安全 | 懒加载 | 说明 |
|---|---|---|---|
| 饿汉式(static final) | 安全 | 否 | 类加载即创建,简单可靠 |
| 懒汉式(synchronized) | 安全 | 是 | 锁开销大,性能差 |
| 双重检查锁(DCL + volatile) | 安全 | 是 | 常用,需 volatile 防指令重排 |
| 静态内部类 Holder | 安全 | 是 | JVM 类加载机制保证安全,推荐 |
| 枚举单例 | 安全 | 是 | 天然防反射与序列化破坏,最推荐 |
public class ConfigManager {
private static volatile ConfigManager instance; // volatile 禁止重排
private ConfigManager() { } // 私有构造
public static ConfigManager getInstance() {
if (instance == null) { // 快速路径,无锁
synchronized (ConfigManager.class) {
if (instance == null) { // 二次检查
instance = new ConfigManager();
}
}
}
return instance;
}
}
2.2 反模式警示
单例本质上是全局可变状态,滥用会导致:隐藏依赖、难以测试(mock)、并发竞争。现代做法更倾向于依赖注入容器(Spring/Guice)管理的单例 Bean,由容器负责生命周期。判据:只有当实例确实是无状态或只读时,单例才是合理选择。
3. 创建型模式:工厂方法与抽象工厂
3.1 简单工厂 vs 工厂方法 vs 抽象工厂
| 模式 | 抽象程度 | 创建对象方式 | 适用场景 |
|---|---|---|---|
| 简单工厂 | 最低 | 静态方法按参数 if-else 分支 | 对象种类少、无需扩展 |
| 工厂方法 | 中 | 每个产品对应一个工厂子类 | 单一产品线、需要按需扩展 |
| 抽象工厂 | 最高 | 一组工厂创建一族产品 | 多个产品族,如 UI 风格(深色/浅色) |
// 工厂方法:把"实例化"推迟到子类
public abstract class Dialog {
public void render() {
Button ok = createButton(); // 调用工厂方法
ok.onClick(() -> close());
ok.paint();
}
protected abstract Button createButton(); // 工厂方法(钩子)
}
public class WindowsDialog extends Dialog {
@Override protected Button createButton() {
return new WindowsButton(); // 子类决定具体产品
}
}
public class HtmlDialog extends Dialog {
@Override protected Button createButton() {
return new HtmlButton();
}
}
3.2 抽象工厂
抽象工厂在工厂方法之上再抽象一层:一个工厂创建一族相关产品(如"Windows 风格按钮 + 复选框"),客户端面向抽象工厂编程,运行时选择具体工厂族(Windows/Mac/Linux)。典型应用:跨平台 UI 主题、数据库驱动族(各数据库 Connection/Statement 均由同一 Driver 工厂产生)。
class GUIFactory: # 抽象工厂接口
def create_button(self): raise NotImplementedError
def create_checkbox(self): raise NotImplementedError
class WindowsFactory(GUIFactory):
def create_button(self): return WindowsButton()
def create_checkbox(self): return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self): return MacButton()
def create_checkbox(self): return MacCheckbox()
工厂模式的收益是解耦「使用方」与「具体类」,让新增产品只新增代码不改旧代码(满足 OCP)。代价是类数量膨胀——小项目直接用构造函数即可,不必强行套工厂。
4. 创建型模式:建造者与原型
4.1 建造者模式(Builder)
当对象构造参数多、参数有可选组合或需不可变时,用建造者分步构造:
public class HttpRequest {
private final String url; // 必填
private final String method; // 可选,默认 GET
private final String body; // 可选
private final int timeout; // 可选,默认 5000
private HttpRequest(Builder b) {
this.url = b.url; this.method = b.method;
this.body = b.body; this.timeout = b.timeout;
}
public static class Builder {
private String url;
private String method = "GET";
private String body = "";
private int timeout = 5000;
public Builder(String url) { this.url = url; }
public Builder method(String m) { this.method = m; return this; }
public Builder body(String b) { this.body = b; return this; }
public Builder timeout(int t) { this.timeout = t; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}
与工厂对比:工厂关注"返回哪种类型",建造者关注"如何分步组装同一个类型"。Lombok
@Builder、OkHttpRequest.Builder都是该模式的生产级应用。
4.2 原型模式(Prototype)
用现有实例通过拷贝快速创建新实例,避免重新执行昂贵的构造过程(如加载配置、初始化连接):
import copy
class Document:
def __init__(self, title="", body="", styles=None):
self.title, self.body = title, body
self.styles = styles or {}
def clone(self): # 原型方法:深拷贝
return copy.deepcopy(self)
doc = Document("模板", "...")
copy_doc = doc.clone() # 成本远低于重新构造
5. 结构型模式:适配器与代理
5.1 适配器(Adapter)
适配器把不兼容的接口转换为目标接口,让已有类能接入新系统,本质是"包装器 + 接口转换"。典型场景:SDK 版本差异、第三方库 API 不同、旧系统遗留代码改造。
// 老系统:用 XML 格式读取报表
public interface LegacyReportParser {
XMLData parseXML(String path);
}
// 新系统期望:返回 JSON
public interface ModernReportParser {
JSONData parse(String path);
}
// 适配器:把老实现包装成新接口
public class ReportAdapter implements ModernReportParser {
private final LegacyReportParser legacy;
public ReportAdapter(LegacyReportParser legacy) { this.legacy = legacy; }
@Override
public JSONData parse(String path) {
XMLData xml = legacy.parseXML(path);
return convert(xml); // 内部做格式转换
}
}
5.2 代理(Proxy)
代理在访问真实对象前后插入控制逻辑,与装饰器不同的是代理控制访问,装饰器增强功能。
| 代理类型 | 用途 | 示例 |
|---|---|---|
| 远程代理 | 隐藏网络细节 | RPC 客户端 Stub |
| 虚拟代理 | 延迟加载重量对象 | 大图占位、懒加载 |
| 保护代理 | 权限控制 | 方法级鉴权 |
| 缓存代理 | 结果缓存 | Redis 读写缓存层 |
// 虚拟代理:Image 为共同接口,RealImage 是昂贵的真实实现
public class ProxyImage implements Image {
private RealImage real;
public void display() {
if (real == null) real = new RealImage(path); // 首次访问才加载
real.display();
}
}
6. 结构型模式:装饰器与组合
6.1 装饰器(Decorator)
装饰器通过层层包裹给对象动态叠加行为,替代继承来扩展功能,规避类爆炸(N 个功能 2^N 个子类)。
// 以 Java I/O 为真实案例:new BufferedInputStream(new FileInputStream(...))
public interface DataSource {
void writeData(String data);
String readData();
}
public class FileDataSource implements DataSource {
// 基础实现:写/读文件
}
public class EncryptionDecorator implements DataSource {
private final DataSource wrappee; // 被装饰对象
public EncryptionDecorator(DataSource source) { this.wrappee = source; }
@Override
public void writeData(String data) {
wrappee.writeData(encrypt(data)); // 写入前加密
}
@Override
public String readData() {
return decrypt(wrappee.readData()); // 读取后解密
}
}
// 使用:new CompressionDecorator(new EncryptionDecorator(new FileDataSource()))
Java
BufferedReader/BufferedInputStream、Pythonfunctools.wraps与 Go 的http.Handler链式中间件,都是装饰器思想的体现。装饰器要求组件接口稳定,若接口频繁变化会非常脆弱。
6.2 组合(Composite)
组合模式让单个对象与对象集合被一致对待,形成树形结构(目录树、UI 组件树、菜单结构):
Component (接口: render)
/ \
Leaf Composite
(文件/按钮) (目录/容器)
children: List<Component>
实现要点:Component 定义统一接口;Leaf 直接实现;Composite 持有子节点列表,render() 时递归调用子节点。调用方无需区分单个对象还是对象集合,正是这种"递归 + 统一接口"使组合模式适用于任意深度的树形结构。
7. 行为型模式:观察者与策略
7.1 观察者(Observer)
观察者定义一对多依赖:主题状态变化时自动通知所有观察者。事件总线、发布订阅、GUI 监听、消息队列都是其变体。
class Subject:
def __init__(self):
self._observers = []
def attach(self, obs): self._observers.append(obs)
def notify(self, event): # 状态变化广播
for obs in self._observers:
obs.update(event)
class Logger:
def update(self, event):
print("[log]", event)
class Metrics:
def update(self, event):
print("[metric]", event)
subject = Subject()
subject.attach(Logger())
subject.attach(Metrics())
subject.notify("user.login") # 两个观察者同时收到事件
观察者解耦「事件产生者」与「消费者」,代价是通知顺序不保证。生产系统常用消息队列做异步化的观察者。
7.2 策略(Strategy)
策略模式把一族可互换的算法封装成独立对象,运行时动态选择,消除大量 if-else 分支:
public interface SortStrategy {
void sort(int[] arr);
}
public class QuickSort implements SortStrategy {
@Override public void sort(int[] arr) { /* 快排 */ }
}
public class HeapSort implements SortStrategy {
@Override public void sort(int[] arr) { /* 堆排 */ }
}
public class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) { this.strategy = strategy; }
public void setStrategy(SortStrategy s) { this.strategy = s; } // 运行时切换
public void execute(int[] arr) { strategy.sort(arr); }
}
与状态模式对比:策略是"如何做"(算法互换,调用方主动切换),状态是"何时做什么"(行为随内部状态自动变化)。
8. 行为型模式:模板方法与状态
8.1 模板方法(Template Method)
模板方法在父类定义算法骨架,把可变步骤延迟到子类实现(好莱坞原则:不要打电话给我们,我们会打给你)。典型:框架的 onCreate / 钩子方法、HTTP 请求处理流程。
public abstract class DataMiner {
// 模板方法:定义固定骨架
public final void mine(String path) {
openFile(path); // 1. 打开文件
extractData(); // 2. 提取数据(子类实现)
parse(); // 3. 解析(子类实现)
closeFile(path); // 4. 关闭文件
}
protected abstract void extractData(); // 抽象步骤
protected abstract void parse();
private void openFile(String p) { /* 公共实现 */ }
private void closeFile(String p) { /* 公共实现 */ }
}
public class CsvDataMiner extends DataMiner {
@Override protected void extractData() { /* 按 CSV 拆分 */ }
@Override protected void parse() { /* 解析列 */ }
}
关键纪律:不要覆盖模板方法本身(final 约束骨架),只覆写钩子步骤。与策略模式的区别:策略用组合替换整个算法,模板用继承覆写部分步骤。
8.2 状态(State)
状态模式把对象在不同状态下的行为拆分为独立类,消除状态判断的巨型 if-else,让状态迁移清晰可维护。典型:订单状态机、TCP 连接状态、播放器状态。
class State: # 状态基类
def next(self, order): raise NotImplementedError
class PendingState(State): # 待支付
def next(self, order):
print("支付成功,进入已支付状态")
order.state = PaidState()
class PaidState(State): # 已支付
def next(self, order):
print("发货,进入已发货状态")
order.state = ShippedState()
class ShippedState(State): # 已发货
def next(self, order):
print("签收,订单完成")
order.state = DoneState()
class Order:
def __init__(self):
self.state = PendingState() # 初始状态
def advance(self): # 状态迁移由当前状态决定
self.state.next(self)
order = Order()
order.advance() # 支付成功
order.advance() # 发货
order.advance() # 完成
| 维度 | 策略模式 | 状态模式 |
|---|---|---|
| 关注点 | 算法的可互换性 | 状态驱动的行为迁移 |
| 状态切换 | 由外部调用方决定 | 由对象内部状态自行决定 |
| 典型结构 | 上下文 + 多个策略 | 上下文 + 多个状态对象互转 |
9. 模式对比、选型与反模式
9.1 按职责归类
| 类别 | 模式 | 一句话记忆 |
|---|---|---|
| 创建型 | 单例 / 工厂 / 抽象工厂 / 建造者 / 原型 | 谁来创建对象 |
| 结构型 | 适配器 / 代理 / 装饰器 / 组合 / 外观 | 对象如何组装 |
| 行为型 | 观察者 / 策略 / 模板方法 / 状态 / 责任链 | 对象之间如何协作 |
9.2 易混淆模式辨析
| 易混组合 | 区别要点 |
|---|---|
| 工厂 vs 建造者 | 工厂决定"类型",建造者决定"组装步骤" |
| 适配器 vs 装饰器 | 适配器改接口,装饰器增行为(接口不变) |
| 代理 vs 装饰器 | 代理控制访问,装饰器增强能力 |
| 策略 vs 状态 | 策略外部换算法,状态内部自迁移 |
| 模板方法 vs 策略 | 模板用继承覆写步骤,策略用组合替换整体 |
9.3 反模式警示
| 反模式 | 危害 | 正确做法 |
|---|---|---|
| God Object(上帝类) | 单类职责过多,难以维护 | 拆分为符合 SRP 的小类 |
| 继承滥用 | 深层继承链,行为难追踪 | 优先组合(composition over inheritance) |
| 单例滥用 | 全局状态污染、难测试 | 用 DI 容器管理生命周期 |
| 过度设计 | 杀鸡用牛刀,代码膨胀 | 遵循 YAGNI,为当前需求而设计 |
| 实现依赖 | 高层依赖具体类 | 面向接口/抽象编程(DIP) |
9.4 选型决策要点
- 只有确定存在变化点才引入模式(YAGNI + 最小代价);
- 优先用组合、函数式参数(高阶函数)、接口默认方法简化传统模式;
- 设计模式是沟通词汇而非银弹——先讨论"为什么"再讨论"用哪个";学习顺序:策略 → 观察者 → 工厂 → 装饰器 → 状态。
参考文章
- Refactoring Guru — Design Patterns
- Wikipedia — SOLID
- OODesign — Gang of Four Patterns
- Source Making — Design Patterns & Refactoring
- GoF《设计模式:可复用面向对象软件的基础》(经典原著)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。