Python 设计模式——建造者模式(Builder Pattern)详解
本文面向零基础新手,从概念到结构、从简单示例到链式调用,全方位讲解建造者模式。
一、什么是建造者模式?
1.1 通俗理解
建造者模式是一种创建型设计模式,核心思想是:
把一个“复杂对象”的构建过程拆成很多小步骤,由“建造者”按步骤一步步组装,最后得到完整对象。这样既能把“怎么造”和“造什么”分开,又能避免构造函数里堆一大堆参数。
可以这样类比:
- 不用建造者:盖房子时,你把“几室几厅、用什么砖、多高、有没有车库”全塞进一个超级长的施工单里,工人容易搞错,以后想加“有没有花园”又要改这一大坨。
- 用建造者:你有一个“建房指挥”(导演),他按步骤来:先打地基(建造者.打地基),再砌墙(建造者.砌墙),再盖顶(建造者.盖顶),再装修(建造者.装修)。每一步由“建造者”完成,最后交给你一栋完整的房子。要加“花园”就多一步“建造者.建花园”,不用把前面步骤全改掉。
所以,建造者模式解决的是:对象有很多部分或很多可选参数、构造过程复杂时,如何让构建清晰、可读、易扩展。
1.2 为什么需要建造者?
先看没有建造者时,复杂对象构造的典型问题。
问题一:构造函数参数爆炸
class Computer:
def __init__(self, cpu, memory, disk, gpu=None, screen_size=None, has_keyboard=True, has_mouse=True, brand=None):
self.cpu = cpu
self.memory = memory
self.disk = disk
self.gpu = gpu
self.screen_size = screen_size
self.has_keyboard = has_keyboard
self.has_mouse = has_mouse
self.brand = brand
# 调用时:参数多、顺序难记、可读性差
pc = Computer("i7", "16G", "512G", "RTX3060", "27寸", True, True, "Dell")
# 谁还记得第几个参数是什么?可选参数一多更乱
问题二:同一类对象,构建过程不同
例如一辆车:有的先装引擎再装轮子,有的先装车身再装引擎;步骤可能不同,但最终都是“一辆车”。如果全塞进一个构造函数里,要么重载很多个 __init__,要么用大量可选参数,都不清晰。
建造者模式的做法是:
- 把“最终产品”(如电脑、汽车)和“怎么一步步造出来”分开。
- “建造者”负责每一步(如
set_cpu、set_memory、build),最后一步返回完整对象。 - 可选部分只在你调用了对应步骤时才加上去,代码可读性好、易扩展。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 对象有很多可选参数 | 如配置对象、请求参数、文档生成选项,避免超长参数列表。 |
| 对象由多个部分组成 | 如电脑(CPU、内存、硬盘…)、汽车(引擎、轮子、车身…),步骤清晰。 |
| 构建过程希望分步骤、可读 | 链式调用如 builder.set_cpu("i7").set_memory("16G").build(),一目了然。 |
| 同一产品有多种构建方式 | 不同“建造者”实现同一套步骤接口,得到不同风格或不同配置的产品。 |
简单记忆:构造复杂、参数多、要分步骤组装时,考虑建造者。
二、建造者模式的结构(四个角色)
建造者模式通常有四个角色,先记住名字和职责即可,后面用代码对照。
| 角色 | 职责 |
|---|---|
| 产品(Product) | 最终要得到的复杂对象,如电脑、汽车、邮件。 |
| 抽象建造者(Builder) | 定义“组装产品需要哪些步骤”的接口(如 set_cpu、set_memory、build)。 |
| 具体建造者(ConcreteBuilder) | 实现每一步怎么设置到产品上,并在 build() 里返回组装好的产品。 |
| 导演(Director) | 可选。按固定顺序调用建造者的步骤,得到一种“套餐”;调用方也可以自己按需调步骤,不用导演。 |
关系可以理解为:
- 导演 使用 建造者 的步骤 → 得到 产品。
- 若没有导演,调用方直接使用建造者,一步步调,最后
build()得到产品。
三、示例一:电脑组装(最简版)
需求:组装一台电脑,有 CPU、内存、硬盘,可选显卡。用建造者分步设置,最后 build() 得到电脑对象。
3.1 产品(Product)
class Computer:
"""最终产品:电脑"""
def __init__(self):
self.cpu = None
self.memory = None
self.disk = None
self.gpu = None
def __str__(self):
parts = [f"CPU:{self.cpu}", f"内存:{self.memory}", f"硬盘:{self.disk}"]
if self.gpu:
parts.append(f"显卡:{self.gpu}")
return " | ".join(parts)
3.2 建造者(Builder)
这里用具体建造者直接实现,省略抽象接口(Python 里不用接口也能看懂;若要抽象,可用 ABC)。
class ComputerBuilder:
"""建造者:负责按步骤组装电脑"""
def __init__(self):
self._computer = Computer()
def set_cpu(self, cpu: str):
self._computer.cpu = cpu
return self # 返回 self 方便链式调用,见下文
def set_memory(self, memory: str):
self._computer.memory = memory
return self
def set_disk(self, disk: str):
self._computer.disk = disk
return self
def set_gpu(self, gpu: str):
self._computer.gpu = gpu
return self
def build(self):
"""返回组装好的产品,并可选择重置建造者以便复用"""
computer = self._computer
self._computer = Computer()
return computer
3.3 使用方式
builder = ComputerBuilder()
computer = (builder
.set_cpu("i7")
.set_memory("16G")
.set_disk("512G")
.set_gpu("RTX3060")
.build())
print(computer) # CPU:i7 | 内存:16G | 硬盘:512G | 显卡:RTX3060
# 不装显卡也可以,不调用 set_gpu 即可
computer2 = builder.set_cpu("i5").set_memory("8G").set_disk("256G").build()
print(computer2) # CPU:i5 | 内存:8G | 硬盘:256G
要点:
- 构建过程是分步骤的,可读性好。
- 可选部分(如显卡)不调就不设,避免构造函数里一堆默认参数。
- 每个 set 方法
return self,可以链式调用(.set_cpu(...).set_memory(...))。
四、链式调用(Fluent Interface)
上面每个 set_xxx 都写了 return self,这样可以在同一行里连续调用多个方法,叫做链式调用(流畅接口):
computer = (builder
.set_cpu("i7")
.set_memory("16G")
.set_disk("512G")
.build())
不返回 self 时,只能这样写,无法链式:
builder.set_cpu("i7")
builder.set_memory("16G")
builder.set_disk("512G")
computer = builder.build()
建造者模式里建议 set 方法都 return self,这样调用方写起来更清晰。
五、导演(Director)——固定“套餐”
有时我们希望固定几种组装顺序或套餐,不用每次自己调一堆步骤。可以加一个“导演”类,内部按顺序调用建造者的方法,对外只暴露“给我来一台高配机”“给我来一台办公机”这样的简单接口。
5.1 导演类
class Director:
"""导演:按固定流程调用建造者,提供“套餐”"""
def __init__(self, builder: ComputerBuilder):
self._builder = builder
def build_high_end(self):
"""高配机套餐"""
return (self._builder
.set_cpu("i9")
.set_memory("32G")
.set_disk("1T")
.set_gpu("RTX4080")
.build())
def build_office(self):
"""办公机套餐"""
return (self._builder
.set_cpu("i5")
.set_memory("8G")
.set_disk("256G")
.build())
5.2 使用导演
builder = ComputerBuilder()
director = Director(builder)
high_end_pc = director.build_high_end()
print(high_end_pc) # CPU:i9 | 内存:32G | 硬盘:1T | 显卡:RTX4080
office_pc = director.build_office()
print(office_pc) # CPU:i5 | 内存:8G | 硬盘:256G
导演的作用:把“高配”“办公”等固定流程封装起来,调用方只需选套餐,不用关心先 set_cpu 还是先 set_memory。若需要完全自定义,仍可以直接用建造者,不用导演。
六、示例二:抽象建造者 + 多种具体建造者
当你有多种建造方式(例如不同品牌、不同风格)时,可以定义抽象建造者(接口),让多种具体建造者实现同一套步骤,但内部实现不同;导演或调用方依赖抽象建造者即可。
6.1 产品
class House:
"""房子:地基、墙、屋顶、装修"""
def __init__(self):
self.foundation = None
self.walls = None
self.roof = None
self.interior = None
def __str__(self):
return f"房子[地基:{self.foundation} 墙:{self.walls} 屋顶:{self.roof} 装修:{self.interior}]"
6.2 抽象建造者(接口)
from abc import ABC, abstractmethod
class HouseBuilder(ABC):
@abstractmethod
def build_foundation(self):
pass
@abstractmethod
def build_walls(self):
pass
@abstractmethod
def build_roof(self):
pass
@abstractmethod
def build_interior(self):
pass
@abstractmethod
def get_house(self) -> House:
pass
6.3 具体建造者一:普通房
class NormalHouseBuilder(HouseBuilder):
def __init__(self):
self._house = House()
def build_foundation(self):
self._house.foundation = "水泥地基"
return self
def build_walls(self):
self._house.walls = "砖墙"
return self
def build_roof(self):
self._house.roof = "瓦顶"
return self
def build_interior(self):
self._house.interior = "简装"
return self
def get_house(self) -> House:
house = self._house
self._house = House()
return house
6.4 具体建造者二:豪华房
class LuxuryHouseBuilder(HouseBuilder):
def __init__(self):
self._house = House()
def build_foundation(self):
self._house.foundation = "钢筋混凝土深地基"
return self
def build_walls(self):
self._house.walls = "保温墙体+大理石饰面"
return self
def build_roof(self):
self._house.roof = "琉璃瓦+太阳能板"
return self
def build_interior(self):
self._house.interior = "精装+智能家居"
return self
def get_house(self) -> House:
house = self._house
self._house = House()
return house
6.5 导演(按固定顺序盖房)
class ConstructionDirector:
def __init__(self, builder: HouseBuilder):
self._builder = builder
def construct(self) -> House:
return (self._builder
.build_foundation()
.build_walls()
.build_roof()
.build_interior()
.get_house())
6.6 使用:同一套步骤,不同建造者得到不同产品
normal_builder = NormalHouseBuilder()
director = ConstructionDirector(normal_builder)
normal_house = director.construct()
print(normal_house) # 房子[地基:水泥地基 墙:砖墙 屋顶:瓦顶 装修:简装]
luxury_builder = LuxuryHouseBuilder()
director = ConstructionDirector(luxury_builder)
luxury_house = director.construct()
print(luxury_house) # 房子[地基:钢筋混凝土深地基 墙:保温墙体+大理石饰面 ...]
要点:导演只依赖 HouseBuilder 抽象,不关心是普通房还是豪华房;换建造者就换产品风格,步骤顺序不变。
七、示例三:配置对象 / 请求参数(无导演)
建造者不一定非要有导演。很多场景下,只是用建造者来分步设置很多可选参数,最后 build() 得到一个配置对象或请求体,调用方自己决定调哪些步骤。
class QueryConfig:
"""查询配置:最终产品"""
def __init__(self):
self.table = None
self.fields = []
self.where = None
self.limit = None
self.offset = None
class QueryBuilder:
def __init__(self):
self._config = QueryConfig()
def table(self, name: str):
self._config.table = name
return self
def select(self, *fields):
self._config.fields = list(fields)
return self
def where(self, condition: str):
self._config.where = condition
return self
def limit(self, n: int):
self._config.limit = n
return self
def offset(self, n: int):
self._config.offset = n
return self
def build(self):
config = self._config
self._config = QueryConfig()
return config
# 使用
config = (QueryBuilder()
.table("users")
.select("id", "name", "email")
.where("age > 18")
.limit(10)
.offset(0)
.build())
# 可读性很好,可选参数只调需要的
八、建造者 vs 其他模式
| 对比 | 建造者 | 工厂 |
|---|---|---|
| 目的 | 分步组装复杂对象,控制构建过程 | 根据类型/参数返回不同产品,隐藏创建细节 |
| 重点 | 步骤多、可选多、过程清晰 | 产品类型多、创建逻辑集中 |
| 典型 | 配置、电脑、文档、请求体 | 动物、支付方式、UI 组件族 |
简单记忆:工厂关心“造哪一种”;建造者关心“怎么一步步造好一个”。
九、常见问题与注意点
9.1 build() 后要不要重置内部产品?
若建造者会被多次使用(如上面 ComputerBuilder 连续造多台电脑),在 build() 里应新建一个“空产品”赋给 self._computer,否则下次 set 会改到已经返回的那台电脑。若建造者只用一次,可以不重置。
9.2 链式调用记得 return self
每个 set/步骤方法最后写 return self,调用方才能链式调用;否则只能一句句写。
9.3 何时要导演?
- 有固定几种“套餐”或固定顺序时,用导演更简洁。
- 完全自定义、每次步骤都不同时,直接使用建造者即可,不必强行加导演。
9.4 不要滥用
对象只有两三个简单参数时,直接构造函数或命名参数即可;建造者适合参数多、可选多、步骤多的复杂对象。
十、小结
- 建造者模式:把复杂对象的构建拆成多步,由建造者按步组装,最后
build()得到产品;可选“导演”封装固定套餐。 - 四个角色:产品、抽象建造者、具体建造者、导演(可选)。
- 链式调用:set 方法
return self,便于写出一长串.set_a().set_b().build()。 - 适用:参数多、部分多、构建过程想分步、可读性要求高时使用。
建议先自己敲一遍“电脑建造者”和“房子建造者”,再试一个带导演的套餐,最后用建造者写一个带很多可选参数的配置类,这样对建造者模式会掌握得比较扎实。