编程素养提升之EffectivePython(Builder篇)

EffectivePython(Builder篇)

  • 〇、前言
  • [一、属性层次的 Builder](#一、属性层次的 Builder)
  • [二、类层次的 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 接口去替代。

解决了抽象和泛型的难题,剩下的代码转化其实就很简单了。

相关推荐
麦麦大数据3 小时前
F032 材料科学文献知识图谱可视化分析系统(四种知识图谱可视化布局) | vue + flask + echarts + d3.js 实现
vue.js·flask·知识图谱·数据可视化·论文文献·1024程序员节·科研图谱
gs801403 小时前
pnpm + webpack + vue 项目依赖缺失错误排查与解决
pnpm·1024程序员节
独自破碎E3 小时前
双堆法求数据流的中位数
1024程序员节
心态还需努力呀4 小时前
异构多活数据架构支持下的医疗业务高可用实践——全栈信创样本分析
1024程序员节
keven-wang4 小时前
网路基础-设备ip地址忘记,有哪些办法可找回设备IP地址?
ip地址·arp·1024程序员节·找设备ip·网络地址解析协议·网络基础协议
墨利昂4 小时前
Git与Gitee使用中的几个问题
1024程序员节
清风6666664 小时前
基于单片机的故障检测自动保护智能防夹自动门设计及LCD状态显示系统
单片机·毕业设计·课程设计·1024程序员节·期末大作业
chenchihwen4 小时前
AI代码开发宝库系列:FAISS向量数据库
数据库·人工智能·python·faiss·1024程序员节
好学且牛逼的马4 小时前
Spring Task 核心解析:从原理到源码的简洁逻辑链
1024程序员节