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

一、属性层次的 Builder
在解决业务需求的各种开发过程中,常常会封装一些同时具有必须属性和可选属性的实体类,为了简化这种类对象的构造代码,通常都会使用 Builder 去创建对象。
1、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 脚本中:
"""
/** 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 = 0def __init__(self, servingSize: int, servings: int):self._servingSize = servingSizeself._servings = servingsdef setCalories(self, calories: int):self._calories = caloriesreturn selfdef setFat(self, fat: int):self._fat = fatreturn selfdef setSodium(self, sodium: int):self._sodium = sodiumreturn selfdef setCarbohydrate(self, carbohydrate: int):self._carbohydrate = carbohydratereturn selfdef build(self):return NutritionFacts(self)@propertydef servingSize(self):return self._servingSize@propertydef servings(self):return self._servings@propertydef calories(self):return self._calories@propertydef fat(self):return self._fat@propertydef sodium(self):return self._sodium@propertydef carbohydrate(self):return self._carbohydrateclass NutritionFacts(object):__servingSize: int__servings: int__calories: int__fat: int__sodium: int__carbohydrate: intdef __init__(self, builder: Builder):self.__servingSize = builder.servingSizeself.__servings = builder.servingsself.__calories = builder.caloriesself.__fat = builder.fatself.__sodium = builder.sodiumself.__carbohydrate = builder.carbohydratedef 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 代码没有什么区别,在类的使用上也是如出一辙:
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脚本中编写)
/** 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 并重写相关抽象方法:
/** 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);}@Overridepublic NyPizza build() {return new NyPizza(this);}@Overrideprotected Builder self() {return this;}}private NyPizza(Builder builder) {super(builder);size = builder.size;}@Overridepublic String toString() {return "NyPizza{" +"size=" + size +", toppings=" + toppings +'}';}
}
再来一个 Calzone 类:
/** 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;}@Overridepublic Calzone build() {return new Calzone(this);}@Overrideprotected Builder self() {return this;}}private Calzone(Builder builder) {super(builder);sauceInside = builder.sauceInside;}@Overridepublic String toString() {return "Calzone{" +"sauceInside=" + sauceInside +", toppings=" + toppings +'}';}
}
而使用代码可以另建一个 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的优秀表现:
"""
/** 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)@propertydef 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@abstractmethoddef build(self) -> Pizza:"""抽象方法:由子类实现,用于构建具体的 Pizza 对象"""pass@abstractmethoddef _self(self: T) -> T:"""抽象方法:返回 'this' 引用,用于方法链"""passdef 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_insidedef __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 selfdef add_sauce_inside(self) -> 'CalzoneBuilder':"""Calzone 特有的方法"""self.sauce_inside = Truereturn 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 接口去替代。
解决了抽象和泛型的难题,剩下的代码转化其实就很简单了。
