02-创建型模式¶
学习时间: 约4-5小时 难度级别: ⭐⭐ 初中级 前置知识: SOLID设计原则、面向对象编程 学习目标: 掌握5种创建型模式的意图、结构、实现和适用场景,能在实际项目中灵活运用
🎯 学习目标¶
- 理解创建型模式解决的核心问题:将对象的创建与使用分离
- 掌握单例模式的多种实现方式和线程安全问题
- 理解工厂方法与抽象工厂的区别和适用场景
- 掌握建造者模式的链式调用设计
- 理解原型模式中浅拷贝与深拷贝的区别
目录¶
- 1. 创建型模式概述
- 2. 单例模式(Singleton)
- 3. 工厂方法模式(Factory Method)
- 4. 抽象工厂模式(Abstract Factory)
- 5. 建造者模式(Builder)
- 6. 原型模式(Prototype)
- 7. 创建型模式对比
- 8. 练习与自我检查
1. 创建型模式概述¶
创建型模式关注对象的创建方式,核心目标是将对象的创建逻辑与使用逻辑解耦,使系统在"创建什么对象"和"如何创建对象"方面更加灵活。
| 模式 | 解决的问题 | 一句话 |
|---|---|---|
| 单例 | 确保全局唯一实例 | "只能有一个" |
| 工厂方法 | 让子类决定创建哪类对象 | "创建交给子类" |
| 抽象工厂 | 创建一族相关对象 | "产品家族" |
| 建造者 | 分步构建复杂对象 | "一步一步来" |
| 原型 | 通过复制创建新对象 | "克隆一个" |
2. 单例模式(Singleton)¶
意图¶
确保一个类只有一个实例,并提供全局访问点。
适用场景¶
- 数据库连接池、线程池
- 配置管理器(加载一次,全局使用)
- 日志管理器
- 缓存管理器
结构(文字描述UML)¶
┌──────────────┐
│ Singleton │
├──────────────┤
│ -instance │ ← 静态私有实例
├──────────────┤
│ -__init__() │ ← 私有构造函数
│ +getInstance()│ ← 公有静态方法
│ +operation() │
└──────────────┘
Python实现¶
方式一:模块天然单例(最Pythonic)
Python的模块在第一次导入时执行,之后从缓存中获取,天然就是单例。
# config.py — 模块本身就是单例
class _Config:
def __init__(self):
self.debug = False
self.db_url = "sqlite:///app.db"
def load(self, path):
# 从文件加载配置
...
config = _Config() # 全局唯一实例
# 使用:from config import config
方式二:使用 __new__ (经典方式)
# ⚠️ 此实现非线程安全:多线程并发调用时,两个线程可能同时通过 `is None` 检查,
# 从而创建多个实例。如需线程安全,请参考下方「方式四:线程安全的单例」
# 或直接使用模块级实例(方式一)。
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
# 注意:__init__每次调用都会执行
if not hasattr(self, '_initialized'):
self._initialized = True
self.data = {}
# 测试
a = Singleton()
b = Singleton()
assert a is b # ✅ 同一个实例
方式三:装饰器方式
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
self.connection = "Connected!"
方式四:线程安全的单例
import threading # 线程池/多线程:并发执行任务
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs): # *args接收任意位置参数;**kwargs接收任意关键字参数
if cls._instance is None: # 第一次检查(无锁,性能优化)
with cls._lock:
if cls._instance is None: # 第二次检查(加锁后,保证安全)
cls._instance = super().__new__(cls) # super()调用父类方法
return cls._instance
Java实现¶
// 方式一:饿汉式(类加载时创建,线程安全)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return INSTANCE; }
}
// 方式二:双重检查锁(懒汉式,线程安全)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) { // synchronized同步锁,保证线程安全
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 方式三:枚举(最佳实践,防反射和序列化攻击)
public enum Singleton {
INSTANCE;
public void doSomething() {
System.out.println("Singleton operation");
}
}
优缺点¶
| 优点 | 缺点 |
|---|---|
| 保证全局唯一实例 | 违反单一职责(管理自身生命周期) |
| 延迟初始化节省资源 | 难以进行单元测试(全局状态) |
| 全局访问点 | 隐藏了类之间的依赖关系 |
实际应用¶
- Python的
logging.getLogger(name)— 同名logger返回同一实例 - Python的
None,True,False— 天然单例 - Java的
Runtime.getRuntime() - Spring框架的Bean默认是单例
3. 工厂方法模式(Factory Method)¶
意图¶
定义一个创建对象的接口,让子类决定实例化哪个类。
适用场景¶
- 不确定需要创建什么具体类的对象
- 希望把对象创建的责任委托给子类
- 框架中由用户决定具体产品(如日志框架)
结构¶
┌───────────────┐ ┌───────────────┐
│ Creator │ │ Product │
│ (abstract) │ │ (abstract) │
├───────────────┤ ├───────────────┤
│ +factoryMethod()│─创建→│ +operation() │
│ +someOperation()│ └───────┬───────┘
└───────┬───────┘ │
│ │
┌───────┴───────┐ ┌──────┴────────┐
│ConcreteCreator│ │ConcreteProduct│
├───────────────┤ ├───────────────┤
│ +factoryMethod()│ │ +operation() │
└───────────────┘ └───────────────┘
Python实现¶
from abc import ABC, abstractmethod
# 产品接口
class Notification(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
# 具体产品
class EmailNotification(Notification):
def send(self, message):
print(f"📧 Email: {message}")
class SMSNotification(Notification):
def send(self, message):
print(f"📱 SMS: {message}")
class PushNotification(Notification):
def send(self, message):
print(f"🔔 Push: {message}")
# 工厂接口
class NotificationFactory(ABC):
@abstractmethod
def create_notification(self) -> Notification:
pass
def notify(self, message):
"""模板方法:使用工厂方法创建对象并发送通知"""
notification = self.create_notification()
notification.send(message)
# 具体工厂
class EmailFactory(NotificationFactory):
def create_notification(self):
return EmailNotification()
class SMSFactory(NotificationFactory):
def create_notification(self):
return SMSNotification()
# 简单工厂(变体:集中创建逻辑)
class SimpleNotificationFactory:
@staticmethod # @staticmethod静态方法,不需要实例
def create(channel: str) -> Notification:
factories = {
"email": EmailNotification,
"sms": SMSNotification,
"push": PushNotification,
}
if channel not in factories:
raise ValueError(f"Unknown channel: {channel}")
return factories[channel]()
# 使用
factory = EmailFactory()
factory.notify("Your order has been shipped!")
notification = SimpleNotificationFactory.create("push")
notification.send("New message received!")
Java实现¶
// 产品接口
public interface Notification {
void send(String message);
}
public class EmailNotification implements Notification {
public void send(String message) {
System.out.println("📧 Email: " + message);
}
}
// 工厂接口
public abstract class NotificationFactory {
public abstract Notification createNotification();
public void notify(String message) {
Notification notification = createNotification();
notification.send(message);
}
}
public class EmailFactory extends NotificationFactory {
public Notification createNotification() {
return new EmailNotification();
}
}
优缺点¶
| 优点 | 缺点 |
|---|---|
| 解耦创建与使用 | 每增加一种产品需要新建一对类 |
| 遵循开闭原则 | 简单场景可能过度设计 |
| 遵循单一职责 | 类的数量增多 |
4. 抽象工厂模式(Abstract Factory)¶
意图¶
提供一个创建一系列相关对象的接口,无需指定它们的具体类。
适用场景¶
- 系统需要独立于产品的创建和组合
- 系统需要配置多个"产品族"(如不同操作系统的UI组件)
- 需要确保同族产品一起使用
Python实现¶
from abc import ABC, abstractmethod
# ====== 抽象产品 ======
class Button(ABC):
@abstractmethod
def render(self): pass
class TextBox(ABC):
@abstractmethod
def render(self): pass
# ====== Windows产品族 ======
class WindowsButton(Button):
def render(self):
return "[Windows Button]"
class WindowsTextBox(TextBox):
def render(self):
return "[Windows TextBox]"
# ====== macOS产品族 ======
class MacButton(Button):
def render(self):
return "(Mac Button)"
class MacTextBox(TextBox):
def render(self):
return "(Mac TextBox)"
# ====== 抽象工厂 ======
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button: pass
@abstractmethod
def create_textbox(self) -> TextBox: pass
class WindowsFactory(UIFactory):
def create_button(self): return WindowsButton()
def create_textbox(self): return WindowsTextBox()
class MacFactory(UIFactory):
def create_button(self): return MacButton()
def create_textbox(self): return MacTextBox()
# ====== 客户端代码 ======
def create_ui(factory: UIFactory):
"""客户端只依赖抽象工厂,不关心具体产品族"""
button = factory.create_button()
textbox = factory.create_textbox()
print(button.render(), textbox.render())
# 根据运行环境选择工厂
import platform
factory = WindowsFactory() if platform.system() == "Windows" else MacFactory()
create_ui(factory)
Java实现¶
public interface UIFactory { // interface定义类型契约
Button createButton();
TextBox createTextBox();
}
public class WindowsFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public TextBox createTextBox() { return new WindowsTextBox(); }
}
优缺点¶
| 优点 | 缺点 |
|---|---|
| 保证产品族一致性 | 增加新产品类型困难(需修改所有工厂) |
| 客户端与具体产品解耦 | 类的数量爆炸式增长 |
| 遵循开闭原则(新增产品族) | 代码复杂度高 |
实际应用¶
- Java AWT / Swing的跨平台UI组件
- 数据库驱动(JDBC的Connection、Statement、ResultSet是一族)
- 游戏引擎中不同平台的渲染器、音频引擎
5. 建造者模式(Builder)¶
意图¶
将一个复杂对象的构建过程与其表示分离,使同样的构建过程可以创建不同的表示。
适用场景¶
- 对象有很多可选参数(避免"望远镜构造函数"反模式)
- 对象创建有多个步骤,步骤顺序可能不同
- 需要创建同一对象的不同变体
Python实现¶
class Computer:
"""产品:电脑"""
def __init__(self):
self.cpu = None
self.ram = None
self.storage = None
self.gpu = None
self.os = None
def __str__(self):
parts = [f"CPU: {self.cpu}", f"RAM: {self.ram}", f"Storage: {self.storage}"]
if self.gpu:
parts.append(f"GPU: {self.gpu}")
if self.os:
parts.append(f"OS: {self.os}")
return " | ".join(parts)
class ComputerBuilder:
"""建造者:链式调用风格"""
def __init__(self):
self._computer = Computer()
def cpu(self, cpu: str) -> 'ComputerBuilder':
self._computer.cpu = cpu
return self # 返回self实现链式调用
def ram(self, ram: str) -> 'ComputerBuilder':
self._computer.ram = ram
return self
def storage(self, storage: str) -> 'ComputerBuilder':
self._computer.storage = storage
return self
def gpu(self, gpu: str) -> 'ComputerBuilder':
self._computer.gpu = gpu
return self
def os(self, os: str) -> 'ComputerBuilder':
self._computer.os = os
return self
def build(self) -> Computer:
"""构建最终产品"""
computer = self._computer
self._computer = Computer() # 重置,以便复用Builder
return computer
# 使用:链式调用,清晰易读
gaming_pc = (ComputerBuilder()
.cpu("Intel i9-13900K")
.ram("32GB DDR5")
.storage("2TB NVMe SSD")
.gpu("RTX 4090")
.os("Windows 11")
.build())
office_pc = (ComputerBuilder()
.cpu("Intel i5-13400")
.ram("16GB DDR4")
.storage("512GB SSD")
.os("Ubuntu 22.04")
.build())
print(gaming_pc)
print(office_pc)
Python进阶:使用 dataclass 简化
from dataclasses import dataclass, field
@dataclass # 自动生成__init__等方法
class ServerConfig:
"""Python中dataclass + 默认值常可替代Builder"""
host: str = "localhost"
port: int = 8080
workers: int = 4
debug: bool = False
timeout: int = 30
ssl: bool = False
cert_path: str = ""
# 使用:命名参数即可
config = ServerConfig(host="0.0.0.0", port=443, ssl=True, cert_path="/etc/ssl/cert.pem")
Java实现¶
public class Computer {
private final String cpu; // 必选
private final String ram; // 必选
private String gpu; // 可选
private String os; // 可选
private Computer(Builder builder) {
this.cpu = builder.cpu;
this.ram = builder.ram;
this.gpu = builder.gpu;
this.os = builder.os;
}
public static class Builder {
private final String cpu;
private final String ram;
private String gpu;
private String os;
public Builder(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}
public Builder gpu(String gpu) { this.gpu = gpu; return this; }
public Builder os(String os) { this.os = os; return this; }
public Computer build() { return new Computer(this); }
}
}
// 使用
Computer pc = new Computer.Builder("i9", "32GB")
.gpu("RTX 4090")
.os("Windows")
.build();
优缺点¶
| 优点 | 缺点 |
|---|---|
| 避免构造函数参数过多 | 代码量增加(需要额外的Builder类) |
| 步骤清晰,代码易读 | 简单对象用Builder是过度设计 |
| 可以创建不可变对象 |
实际应用¶
- Java的
StringBuilder - OkHttp的
Request.Builder() - Lombok的
@Builder注解 - SQL查询构建器
6. 原型模式(Prototype)¶
意图¶
通过复制已有对象来创建新对象,避免重复初始化的开销。
适用场景¶
- 对象创建代价大(如涉及数据库查询、网络请求)
- 需要创建与已有对象相似的新对象
- 需要在运行时动态指定产品类型
浅拷贝 vs 深拷贝¶
import copy
class Address:
def __init__(self, city, street):
self.city = city
self.street = street
class Person:
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address # 引用类型
def clone(self):
"""浅拷贝:内部的引用对象仍指向同一实例"""
return copy.copy(self)
def deep_clone(self):
"""深拷贝:完全独立的副本"""
return copy.deepcopy(self)
# 演示区别
original = Person("Alice", 30, Address("Beijing", "Main St"))
# 浅拷贝
shallow = original.clone()
shallow.name = "Bob" # 修改值类型 → 不影响原对象 ✅
shallow.address.city = "Shanghai" # 修改引用类型 → 影响原对象!❌
print(original.address.city) # "Shanghai" — 被修改了!
# 深拷贝
deep = original.deep_clone()
deep.address.city = "Shenzhen"
print(original.address.city) # "Shanghai" — 不受影响 ✅
Python实现¶
from abc import ABC, abstractmethod # ABC抽象基类;abstractmethod强制子类实现
import copy
class Prototype(ABC):
@abstractmethod
def clone(self):
pass
class GameCharacter(Prototype):
def __init__(self, name, level, skills, equipment):
self.name = name
self.level = level
self.skills = skills # list
self.equipment = equipment # dict
def clone(self):
return copy.deepcopy(self)
def __str__(self):
return f"{self.name} (Lv.{self.level}) Skills:{self.skills}"
# 原型注册表
class CharacterRegistry:
def __init__(self):
self._prototypes = {}
def register(self, name, prototype):
self._prototypes[name] = prototype
def create(self, name, **overrides):
prototype = self._prototypes.get(name)
if prototype is None:
raise ValueError(f"Unknown prototype: {name}")
clone = prototype.clone()
for key, value in overrides.items():
setattr(clone, key, value) # hasattr/getattr/setattr动态操作对象属性
return clone
# 注册原型
registry = CharacterRegistry()
registry.register("warrior", GameCharacter(
"Warrior", 1, ["Slash", "Block"], {"weapon": "Sword", "armor": "Shield"}
))
registry.register("mage", GameCharacter(
"Mage", 1, ["Fireball", "Heal"], {"weapon": "Staff", "armor": "Robe"}
))
# 基于原型创建新角色
player1 = registry.create("warrior", name="Arthur", level=10)
player2 = registry.create("mage", name="Merlin", level=8)
print(player1) # Arthur (Lv.10) Skills:['Slash', 'Block']
print(player2) # Merlin (Lv.8) Skills:['Fireball', 'Heal']
Java实现¶
public class GameCharacter implements Cloneable { // extends继承;implements实现接口
private String name;
private int level;
private List<String> skills;
@Override // @Override重写父类方法
public GameCharacter clone() {
try { // try/catch捕获异常
GameCharacter clone = (GameCharacter) super.clone();
clone.skills = new ArrayList<>(this.skills); // 深拷贝集合
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
优缺点¶
| 优点 | 缺点 |
|---|---|
| 减少重复初始化开销 | 深拷贝复杂对象可能很昂贵 |
| 动态决定产品类型 | 循环引用的深拷贝需小心 |
| 简化创建过程 | 需要为每个类实现clone方法 |
实际应用¶
- Python的
copy.copy()/copy.deepcopy() - JavaScript的
Object.assign()/ spread operator - Java的
Cloneable接口 - 游戏中从"模板"创建大量相似的NPC/敌人
7. 创建型模式对比¶
| 模式 | 核心问题 | 创建方式 | 产品类型 |
|---|---|---|---|
| 单例 | 限制实例数量 | 类自身控制 | 单一类 |
| 工厂方法 | 推迟到子类创建 | 继承 + 多态 | 单一产品 |
| 抽象工厂 | 创建一族产品 | 组合 + 多态 | 产品族 |
| 建造者 | 分步构建复杂对象 | 步骤化构建 | 复杂对象 |
| 原型 | 通过复制创建 | 克隆已有实例 | 任意类型 |
选择指南: - 只需要一个实例 → 单例 - 创建过程简单但类型不确定 → 工厂方法 - 需要创建一组配套的对象 → 抽象工厂 - 对象很复杂、有很多可选配置 → 建造者 - 需要创建已有对象的变体 → 原型
8. 练习与自我检查¶
✏️ 练习题¶
-
实现线程安全单例:用Python实现一个线程安全的数据库连接池单例类,支持
get_connection()和release_connection()方法。 -
工厂方法实践:设计一个日志系统,支持输出到控制台、文件、远程服务器。使用工厂方法模式,让新增日志目标不需要修改已有代码。
-
抽象工厂应用:设计一个跨数据库ORM,使用抽象工厂创建不同数据库(MySQL、PostgreSQL、SQLite)的Connection、Cursor、Query对象。
-
建造者实践:为HTTP请求构建一个Builder,支持设置URL、Method、Headers、Body、Timeout等参数,链式调用。
-
原型模式应用:在一个游戏中,基于"兵种模板"克隆大量士兵(步兵、弓兵、骑兵),每个士兵有独立的HP和位置但共享基础属性。
-
模式选择:以下场景分别应该使用哪种创建型模式?
- 全局配置管理器
- 根据用户输入创建不同的图表类型(柱状图/折线图/饼图)
- 构建包含多个可选字段的SQL查询
- 根据操作系统创建对应的文件对话框+菜单栏+工具栏
面试要点¶
Q1: 实现单例模式有哪些方式?各有什么优缺点? A: 饿汉式(类加载时创建,线程安全但不能延迟加载)、懒汉式+双重检查锁(延迟加载,线程安全)、枚举(Java最佳,防反射和序列化)、模块天然单例(Python最佳)。
Q2: 工厂方法和抽象工厂的区别? A: 工厂方法创建一种产品,通过继承让子类决定具体类型;抽象工厂创建一族相关产品,保证产品族的一致性。抽象工厂中每个方法实际上就是一个工厂方法。
Q3: 什么时候用建造者模式而不是构造函数? A: 当对象有4个以上的参数(尤其是可选参数多),或者构建过程有多个步骤,或者需要构造不可变对象时。Java中尤其推荐(Python可以用dataclass+默认参数替代简单场景)。
Q4: 深拷贝和浅拷贝的区别? A: 浅拷贝只复制对象本身和其直接属性的值(引用类型只复制引用,共享底层对象);深拷贝递归复制所有层次的对象,得到完全独立的副本。
自我检查清单¶
- 能用至少两种方式实现Python单例
- 理解Java中枚举单例为什么是最佳实践
- 能区分简单工厂、工厂方法、抽象工厂
- 能实现链式调用的Builder模式
- 理解浅拷贝和深拷贝的区别,能举例说明
- 能根据场景选择合适的创建型模式
下一章: 03-结构型模式 — 学习如何优雅地组合对象