EffectivePython(Builder篇)
- 〇、前言
- [一、属性层次的 Builder](#一、属性层次的 Builder)
-
- [1、Java 代码](#1、Java 代码)
- 2、Python
- [二、类层次的 Builder](#二、类层次的 Builder)
〇、前言
Java 由于其优秀的网络编程能力而广受欢迎,至今仍然在服务端领域发光发热。许多年来,不乏有杰出的 Java 开发者,充分结合 Java 的面向对象编程特性,设计出一种种规范而高效的范式代码,比如 《Effective Java》中的案例代码。
Python 同样也是面向对象编程语言,那么为 Java 量身定做的范式代码,使用Python语言是否具备重现性呢?如果同样适用于 Python,又该如何编写才能做到规范而高效呢?
本系列将以 《Effective Java》的案例代码为范本,提供一些 Effective 的 Python 代码,从而帮助大家进一步提高Python编程能力。

一、属性层次的 Builder
在解决业务需求的各种开发过程中,常常会封装一些同时具有必须属性和可选属性的实体类,为了简化这种类对象的构造代码,通常都会使用 Builder 去创建对象。
1、Java 代码
首先,看一下 Java 版的案例代码:
java
/*
* Copyright (c) 2025/10/20 彭友聪
* EffectiveJava is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/20 21:40
* file: NutritionFacts.java
* IDE: IDEA
* */
package com.pyc.builder_pattern;
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int carbohydrate;
private final int sodium;
public static class Builder {
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
public String toString() {
return "servingSize: " + servingSize +
" servings: " + servings +
" calories: " + calories +
" fat: " + fat +
" carbohydrate: " + carbohydrate +
" sodium: " + sodium;
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
carbohydrate = builder.carbohydrate;
sodium = builder.sodium;
}
public String toString() {
return "servingSize: " + servingSize +
" servings: " + servings +
" calories: " + calories +
" fat: " + fat +
" carbohydrate: " + carbohydrate +
" sodium: " + sodium;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.fat(0)
.build();
System.out.println(cocaCola.toString());
}
}
2、Python
由于本文重点在 Python,所以 Java 代码就不进行过多的分析。
熟知 Python 语法的开发者,都应该清楚 Python 并不具备 Java 那般的内部类 能力,但是,Python 所具备的模块 能力,在一定程度上可以用来替代内部类。所以,在Python代码中,NutritionFacts 和对应的 Builder 应当放在相同的 Python 脚本中:
python
"""
/*
* Copyright (c) 彭友聪
* EffectivePython is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/24 20:03
* project: EffectivePython
* file: NutritionFacts.py
* product: Pycharm
* */
"""
class Builder(object):
_servingSize: int
_servings: int
_calories: int = 0
_fat: int = 0
_sodium: int = 0
_carbohydrate: int = 0
def __init__(self, servingSize: int, servings: int):
self._servingSize = servingSize
self._servings = servings
def setCalories(self, calories: int):
self._calories = calories
return self
def setFat(self, fat: int):
self._fat = fat
return self
def setSodium(self, sodium: int):
self._sodium = sodium
return self
def setCarbohydrate(self, carbohydrate: int):
self._carbohydrate = carbohydrate
return self
def build(self):
return NutritionFacts(self)
@property
def servingSize(self):
return self._servingSize
@property
def servings(self):
return self._servings
@property
def calories(self):
return self._calories
@property
def fat(self):
return self._fat
@property
def sodium(self):
return self._sodium
@property
def carbohydrate(self):
return self._carbohydrate
class NutritionFacts(object):
__servingSize: int
__servings: int
__calories: int
__fat: int
__sodium: int
__carbohydrate: int
def __init__(self, builder: Builder):
self.__servingSize = builder.servingSize
self.__servings = builder.servings
self.__calories = builder.calories
self.__fat = builder.fat
self.__sodium = builder.sodium
self.__carbohydrate = builder.carbohydrate
def toString(self):
return "NutritionFacts(servingSize={}, servings={}, calories={}, fat={}, sodium={}, carbohydrate={})".format(
self.__servingSize, self.__servings, self.__calories, self.__fat, self.__sodium, self.__carbohydrate)
由于 Builder 不是 NutritionFacts 的内部类,所以 NutritionFacts 类构造方法中,并不能直接访问 Builder 的非公有成员字段,必须通过属性方法去访问,而这也是 Java 代码转成 Python 代码所必须改造的部分,至于其他部分,形式上跟 Java 代码没有什么区别,在类的使用上也是如出一辙:
python
if __name__ == '__main__':
nutritionFacts = Builder(240, 8).setCalories(100).setSodium(35).setCarbohydrate(27).build()
print(nutritionFacts.toString())
二、类层次的 Builder
Builder 除了可以针对属性进行量身打造,还能利用递归泛型设计类层次的 Builder。
1、Java
用 Java 实现,需要用三个脚本进行编写,首先看看最基础的抽象类 Pizza(Pizza.java脚本中编写)
java
/*
* Copyright (c) 2025/10/24 彭友聪
* EffectiveJava is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/24 20:17
* file: Pizza.java
* IDE: IDEA
* */
package com.pyc.builder_pattern;
import java.util.EnumSet;
import java.util.Set;
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
abstract Pizza build();
protected abstract T self();
public T addTopping(Topping topping) {
return self();
}
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
定义一个 NyPizza 类去继承 Pizza 并重写相关抽象方法:
java
/*
* Copyright (c) 2025/10/24 彭友聪
* EffectiveJava is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/24 20:22
* file: NyPizza.java
* IDE: IDEA
* */
package com.pyc.builder_pattern;
import java.util.Objects;
public class NyPizza extends Pizza{
public Size getSize() {
return size;
}
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
@Override
public String toString() {
return "NyPizza{" +
"size=" + size +
", toppings=" + toppings +
'}';
}
}
再来一个 Calzone 类:
java
/*
* Copyright (c) 2025/10/24 彭友聪
* EffectiveJava is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/24 20:28
* file: Calzone.java
* IDE: IDEA
* */
package com.pyc.builder_pattern;
public class Calzone extends Pizza {
private final boolean sauceInside;
public boolean isSauceInside() {
return sauceInside;
}
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public Calzone build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
@Override
public String toString() {
return "Calzone{" +
"sauceInside=" + sauceInside +
", toppings=" + toppings +
'}';
}
}
而使用代码可以另建一个 Java 脚本进行体验:
java
package com.pyc.builder_pattern;
public class TestCreatePizza {
public static void main(String[] args) {
NyPizza pizza = new NyPizza.Builder(NyPizza.Size.SMALL)
.addTopping(NyPizza.Topping.SAUSAGE)
.addTopping(NyPizza.Topping.ONION)
.build();
Calzone calzone = new Calzone.Builder()
.addTopping(NyPizza.Topping.HAM)
.sauceInside()
.build();
System.out.println(pizza.toString());
System.out.println(calzone.toString());
}
}
2、Python
用 Python 去实现这种递归泛型的 Builder,要省事多了,在一个 Python 脚本中就能完成,而这也是模块能力赋予Python的优秀表现:
python
"""
/*
* Copyright (c) 彭友聪
* EffectivePython is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*
* Author: 彭友聪
* email:2923616405@qq.com
* date: 2025/10/24 20:32
* project: EffectivePython
* file: Pizza.py
* product: Pycharm
* */
"""
from abc import ABC, abstractmethod
from enum import Enum
from typing import Set, TypeVar, Generic
from copy import deepcopy
# 定义 Topping 枚举
class Topping(Enum):
HAM = "火腿"
MUSHROOM = "蘑菇"
ONION = "洋葱"
PEPPER = "青椒"
SAUSAGE = "香肠"
# 为类型提示定义类型变量 (可选,用于 IDE 提示)
T = TypeVar('T', bound='Builder')
# 抽象基类 Pizza
class Pizza(ABC):
"""
披萨抽象基类
"""
def __init__(self, builder: 'Builder'):
"""
私有构造函数,只能由 Builder 调用
"""
# 使用 deepcopy 模拟 Java 的 clone()
self._toppings: Set[Topping] = deepcopy(builder.toppings)
@property
def toppings(self) -> Set[Topping]:
"""
只读属性,返回配料集合的副本,保证不可变性
"""
return deepcopy(self._toppings)
def __str__(self):
return f"{self.__class__.__name__}(toppings={self.toppings})"
# 抽象 Builder 类
class Builder(ABC, Generic[T]):
"""
抽象 Builder 类
"""
def __init__(self):
self.toppings = set() # 使用 set 模拟 EnumSet
@abstractmethod
def build(self) -> Pizza:
"""
抽象方法:由子类实现,用于构建具体的 Pizza 对象
"""
pass
@abstractmethod
def _self(self: T) -> T:
"""
抽象方法:返回 'this' 引用,用于方法链
"""
pass
def add_topping(self, topping: Topping) -> T:
"""
添加配料,并返回 self 以支持链式调用
"""
self.toppings.add(topping)
return self._self() # 返回子类实例
# ==================== 使用示例:实现一个具体的披萨和它的 Builder ====================
class Calzone(Pizza):
"""
具体披萨类:Calzone(一种折叠披萨)
"""
def __init__(self, builder: 'CalzoneBuilder'):
super().__init__(builder)
self.sauce_inside = builder.sauce_inside
def __str__(self):
base = super().__str__()
return f"{base}, sauce_inside={self.sauce_inside}"
class CalzoneBuilder(Builder['CalzoneBuilder']):
"""
Calzone 的具体 Builder
"""
def __init__(self):
super().__init__()
self.sauce_inside = False # 特有属性
def _self(self) -> 'CalzoneBuilder':
"""
实现 _self(),返回自身
"""
return self
def add_sauce_inside(self) -> 'CalzoneBuilder':
"""
Calzone 特有的方法
"""
self.sauce_inside = True
return self # 支持链式调用
def build(self) -> Calzone:
"""
构建 Calzone 实例
"""
return Calzone(self)
# ==================== 演示使用 ====================
if __name__ == "__main__":
# 创建 Calzone 并使用链式调用
calzone = (CalzoneBuilder()
.add_topping(Topping.HAM)
.add_topping(Topping.MUSHROOM)
.add_sauce_inside() # 调用特有方法
.add_topping(Topping.ONION)
.build()) # 最后 build() 得到最终对象
print(calzone)
# 输出: Calzone(toppings={<Topping.HAM: '火腿'>, <Topping.MUSHROOM: '蘑菇'>, <Topping.ONION: '洋葱'>}), sauce_inside=True
# 验证 toppings 是副本,外部无法修改
print("原始 toppings:", calzone.toppings)
calzone.toppings.add(Topping.SAUSAGE) # 修改的是副本,不影响内部
print("修改副本后 toppings:", calzone.toppings)
print("内部 toppings 仍为:", calzone.toppings) # 内部数据未变
在 Python 中,abstract 并不是关键字,实现抽象类的定义必须借助 abc 模块的 ABC 接口。Java 中的泛型,在 Python 中也同样找不到直接形式的语法,但可以通过 typing 模块的 Generic 接口去替代。
解决了抽象和泛型的难题,剩下的代码转化其实就很简单了。