python设计模式–建造者模式(Builder Pattern)

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_cpuset_memorybuild),最后一步返回完整对象。
  • 可选部分只在你调用了对应步骤时才加上去,代码可读性好、易扩展。

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()
  • 适用:参数多、部分多、构建过程想分步、可读性要求高时使用。

建议先自己敲一遍“电脑建造者”和“房子建造者”,再试一个带导演的套餐,最后用建造者写一个带很多可选参数的配置类,这样对建造者模式会掌握得比较扎实。

发表评论