18. 设计模式与面向对象基础

系统掌握面向对象基础与 SOLID 原则,详解创建型(单例/工厂/建造者/原型)、结构型(适配器/装饰器/代理/组合)、行为型(观察者/策略/模板方法/状态)三大类设计模式,含 UML 类图、Java/Python 示例与反模式剖析。

1. 面向对象基础与 SOLID 原则

1.1 面向对象的四大特性

封装:隐藏内部状态,仅暴露必要接口,降低耦合。
继承:子类复用父类结构,表达 is-a 关系(Java 单继承、接口多实现)。
多态:同一接口在不同运行时类型上有不同行为,依赖抽象而非具体。
抽象:抽取共同特征,用抽象类/接口约束契约。

设计目标:高内聚(模块内部职责单一)、低耦合(模块之间依赖最小化)、对修改关闭对扩展开放(OCP)。

1.2 五大设计原则(SOLID)

原则全称核心含义违反的典型症状
SSingle Responsibility一个类只有一个变更理由类方法超过 20 个、工具类泛滥
OOpen/Closed对扩展开放,对修改关闭每加功能就改老类
LLiskov Substitution子类可替换父类且不破坏行为Rectangle 继承 Square 后 setWidth 语义崩坏
IInterface Segregation接口按职责拆分,不强迫实现不需要的方法一个巨型接口,实现类被迫写空实现
DDependency 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、OkHttp Request.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、Python functools.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 选型决策要点

  1. 只有确定存在变化点才引入模式(YAGNI + 最小代价);
  2. 优先用组合、函数式参数(高阶函数)、接口默认方法简化传统模式;
  3. 设计模式是沟通词汇而非银弹——先讨论"为什么"再讨论"用哪个";学习顺序:策略 → 观察者 → 工厂 → 装饰器 → 状态。

参考文章

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机基础」更多文章

  1. 22. CPU 缓存与一致性
  2. 21. 传输层与 TCP 深入
  3. 20. 编译原理基础