Python 设计模式之建造者模式

news2024/9/22 15:45:21

文章目录

    • 建造者模式中的简单版本
      • 逐渐复杂的问题
      • 建造者模式的实现
    • 建造者模式中的经典版本

建造者(builder)模式属于创建型模式,建造者模式一般有两种类型的应用

建造者模式中的简单版本

逐渐复杂的问题

假设现在需要创建一个用户对象,那么你会这样实现代码

class User:
    def __init__(self, name):
        self.name = name

user = User("jack")

后来你需要为这个用户对象引入其他的属性:生日、住址、身高、体重、兴趣,不同的用户在创建时不愿意提供其中一些信息,于是你实现了下面的版本:

class User:
    def __init__(self, name, age=None,birth_date=None,address=None,heigh=None,weight=None,hobby=None):
        self.name = name
        self.age = age
        self.birth_date = birth_date
        self.address = address
        self.heigh = heigh
        self.weight = weight
        self.hobby = hobby

user = User("John", heigh=165,weight=58,hobby="reading")

可以看到随着对象的属性越来越多,我们的构造函数的参数列表越来越长。这里由于使用了关键字参数的关系,所以问题并不明显,假如使用位置参数,那么使用者在使用时就必须记住参数的位置,甚至对于没有的属性需要放置None值:

def __init__(self, name, age,birthday,address,heigh,weight,hobby):
    ...

User("Lucy",17,None,None,68,None,None)

当然这个问题也不是不能接受,让我们继续增加问题的复杂度,假如现在我们还需要对一些属性进行校验或者转化处理,那么你可能会这么修改代码:

class User:
    def __init__(self, name, age=None,birth_date=None,address=None,heigh=None,weight=None,hobby=None):
        self.name = name
        self.age = age if isinstance(age,int) and age > 0 else None
        self.birth_date = birth_date
        self.address = address
        self.heigh = heigh if isinstance(heigh, int) else None
        self.weight = weight if isinstance(weight, int) else None
        self.hobby = self.set_hobby(hobby)

    def set_hobby(self, hobby):
        if isinstance(hobby, list):
            hobby = ",".join(hobby)
        return hobby

user = User("John", heigh=165,weight=58,hobby=["reading","running"])

在这个版本中,构造参数依然很多,但set_hobby的出现,让我们开始思考属性的添加为什么不采用调用实例函数的方式设置?于是我们修改了整个 User 的实现

class User:
    def __init__(self, name):
        self.name = name
        self.label = None
    
    def get_user_info(self):
        return self.__dict__

    def add_age(self,age:int):
        if age < 0:
            raise ValueError("Age should be > 0!!!")
        self.age = age

    def add_birthday(self,birthday):
        self.birthday = birthday

    def add_address(self,address:str):
        self.address = address

    def add_height(self,height:int):
        self.height = height

    def add_weight(self,weight:int):
        self.weight = weight

    def add_hobby(self, hobby):
        if isinstance(hobby, list):
            hobby = ",".join(hobby)
        self.hobby = hobby

def main():
    user = User("John")
    user.add_height(165)
    user.add_weight(58)
    user.add_hobby(["reading","running"])
    print(user.get_user_info())

main()

如果没有再继续添加需求和限制的话,这个版本已经解决了问题。为了让你明白我们为啥要用建造者模式并引入一个 simple builder version,我们增加多几个限制:

  1. 用户实例需要是一次生成的,它是不可变对象,比如下面这种场景:
    1. 如果我们根据这个用户所有的属性来生成一个值,定义它的 __hash__ 方法,由于属性值的持续变化就会 hash 造成前后不一致
  2. 我们不希望用户实例的所有属性在还没完全确定下来之前就被客户使用,这会造成错误。比如下面这种场景:
    1. 如果这是一个汽车实例,我们不能在还没有完全制造好之前就交付给客户使用
    2. 回到这个案例,假设客户端代码这样写:
def main():
    user = User("John")
    user.add_height(165)
    user.add_weight(58)
    if user.hobby is None:
        print(f"{user.name} is a man without any hobby!")
        user.label = "boring man!"
        
    user.add_hobby(["reading","running"])
    print(user.get_user_info())

main()

显然我们会得到

AttributeError: 'User' object has no attribute 'hobby'

user 还没完全定义完全的时候,我们对 user 做其他操作时发现并没有个 hobby 这个属性(即使我们可以用属性 getattr 判断来规避这个异常,我们也错误的给 user 贴了一个 boring 的标签,而实际上他并不是)
在上面这个问题中,我们发现了3个限制和需求:

  1. 繁琐复杂的构造参数,存在不同的组合情况
  2. 传入参数时需要校验或者转化
  3. 实例在完全生成之前不允许交付给第三方使用

建造者模式的实现

聊到这里,我们就要引入建造者模式来解决问题了。先直接看建造者模式的实现:

class User:
    def __init__(self,name,age,birthday,address,weight,height,hobby):
        self.name = name
        self.age = age
        self.birthday = birthday
        self.address = address
        self.weight = weight
        self.height = height
        self.hobby = hobby
        self.label = None
    
    def get_user_info(self):
        return self.__dict__

class Builder:
    def __init__(self):
        self.name = None
        self.age = None
        self.birthday = None
        self.weight = None
        self.height = None
        self.address = None
        self.hobby = None
    
    def get_user(self):
        return User(
            name=self.name,
            age=self.age,
            birthday=self.birthday,
            address=self.address,
            hobby=self.hobby,
            weight=self.weight,
            height=self.height
        )

    def set_name(self, name):
        self.name = name
        return self
        
    def set_age(self,age:int):
        if age < 0:
            raise ValueError("Age should be > 0!!!")
        self.age = age
        return self

    def set_birthday(self,birthday):
        self.birthday = birthday
        return self

    def set_address(self,address:str):
        self.address = address
        return self

    def set_height(self,height:int):
        self.height = height
        return self

    def set_weight(self,weight:int):
        self.weight = weight
        return self

    def set_hobby(self, hobby):
        if isinstance(hobby, list):
            hobby = ",".join(hobby)
        self.hobby = hobby
        return self

def main():
    builder = Builder()
    builder.set_name("John").set_height(165).set_weight(58)
    builder.set_hobby(["reading","running"])
    user = builder.get_user()
    print(user.get_user_info())

main()

在这个实现中,我引入了 builder 来完成对象 User 的创建,它隐藏了 User 对象构造函数中复杂的参数。你可能会觉得这个实现看起来更复杂了,确实是这样,这是这种设计模式在简单对象上的缺点,但如果现在这个对象的属性足够复杂,并且需要根据不同的需求生成不同风格的 user及对应的响应步骤呢?
总结下这个实现的调用逻辑:

client builder User user builder.set_name("John") builder.set_age(13) get_user() 里隐藏了定制的细节 返回 user 实例 这是你想要的 user 实例 client builder User user

到此为止,你已经接触了建造者模式的初级版本!

建造者模式中的经典版本

前面我花了很大的篇幅去引入建造者模式的简单版本,因为我想让你明白为什么不用更常规且直观的方式。
建造者模式通常由下面的组件构成:

  • 产品(Product):要构建的复杂对象。产品类通常包含多个部分或属性。
  • 抽象建造者(Builder):定义了构建产品的抽象接口,包括构建产品的各个部分的方法。
  • 具体建造者(Concrete Builder):实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品。
  • 指导者(Director):负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式。

现在我们来设计一个案例以便介绍经典模式下的建造者模式实现

有一个公司的业务是帮别人开公司,而开公司的流程一般是:

1. 注册公司
2. 注册商标
3. 制定公司规范和文化
4. 招人

业务进行的过程中有一些不同的地方:
1. 成立海外公司和中国公司的注册地不一样
2. 公司文化也不一样
4. 用户会根据他的需求添加一些部门(法务部、人事部、行政部、研发部、销售部)

而建造者模式可以做到将 Company 对象的构造过程划分为一组步骤, 比如先注册公司再申请 logo等等。 每次创建对象时,你都需要通过生成器对象执行一些步骤。 重点在于你无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可,比如给公司起名字这个步骤无法省略。
当你需要创建不同形式的产品时, 其中的一些构造步骤可能需要不同的实现。 例如, 国内公司的注册流程和海外公司的注册流程差距很大,logo 的设计风格也不一样。
在这种情况下, 你可以创建多个不同的 builder , 用不同方式实现一组相同的创建步骤。 然后你就可以在创建过程中使用这些 builder(例如按顺序调用多个构造步骤) 来生成不同类型的对象。

在这个指导原则下,应用这个模式的类关系图:
```

代码实现

from abc import ABC, abstractmethod

class Company:
    def __init__(self, logo, company_name,company_register_address,document, department_list):
        self.logo = logo
        self.company_name = company_name
        self.company_register_address = company_register_address
        self.document = document
        self.department_list = department_list

    def __str__(self):
        s = ""
        for k, v in self.__dict__.items():
            if k == "department_list":
                s += "department_list:\n"
                for i in v:
                    s += f"{i}\n"
            else:
                s += f"{k}:{v}\n"
        return s
    
class Builder(ABC):
    def __init__(self):
        self.company_name = None
        self.logo = None
        self.department_list = []
    
    def register_company(self):
        raise NotImplementedError

    def register_logo(self):
        raise NotImplementedError

    def define_document(self):
        raise NotImplementedError

    def set_department(self, department_name,people_counts):
        self.department_list.append((department_name, people_counts))
        return self
    
    def set_company_name(self,name):
        self.company_name = name
        return self
    
    def get_company(self):
        self.register_company()
        self.register_logo()
        self.define_document()
        
        return Company(
            logo=self.logo,
            company_name=self.company_name,
            company_register_address=self.company_register_address,
            document=self.document,
            department_list=self.department_list
        )

class AboardCompanyBuilder(Builder):
    def register_logo(self):
        self.logo = "aboard logo"
        print("Do a aboard style logo")
        print("-"*10)

    def register_company(self):
        # do some specify step
        print("-"*10)
        print("注册流程:")
        print("前往海外申请")
        print("校验是否由海外法人")
        print("公示一个月")
        print("-"*10)
        self.company_register_address = "aboard"

    def define_document(self):
        self.document = "aboard culture company rules"
        

class LocalCompanyBuilder(Builder):
    
    def register_logo(self):
        self.logo = "local logo"
        print("Do a local style logo")
        print("-"*10)

    def register_company(self):
        # do some specify step
        print("-"*10)
        print("注册流程:")
        print("在国内申请")
        print("需要公示一周")
        print("-"*10)
        self.company_register_address = "local"

    def define_document(self):
        self.document = "aboard culture company rules"    

    def set_department(self, department_name, people_counts):
        if people_counts > 5:
            # 将人数 +2
            self.department_list.append((department_name, people_counts+2))
        else:
            self.department_list.append((department_name, people_counts))
        return self

def main():
    builder = LocalCompanyBuilder()
    builder.set_company_name("Big Company")
    builder.set_department("人事部",6).set_department("研发部",2)
    company = builder.get_company()
    print(company)

main()

现在我们对比下两种 Builder 的输出:

----------
注册流程:
在国内申请
需要公示一周
----------
Do a local style logo
----------
logo:local logo
company_name:Big Company
company_register_address:local
document:aboard culture company rules
department_list:
('人事部', 8)
('研发部', 2)
----------
注册流程:
前往海外申请
校验是否由海外法人
公示一个月
----------
Do a aboard style logo
----------
logo:aboard logo
company_name:Big Company
company_register_address:aboard
document:aboard culture company rules
department_list:
('人事部', 6)
('研发部', 2)

通过这种方式,我们向 client 隐藏了具体 Company 的生成步骤(注册公司、logo、文化文档),而 client 只需关注他需要创建的公司是哪种类型的(海外公司还是国内公司,不同类型的公司它的行为和属性是不同的),并且他可以任意的添加他想要的部分(部门,当然这里将这个实现简略为往列表中添加元组,而在其他例子中,可能会用菜品或者零件来替代)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1973990.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何提高小红书种草转化率

企业和品牌方想要在小红书上做种草推广一般分为图文种草和视频种草两种形式&#xff0c;而且小红书是一个完全可以依靠内容实现涨粉、变现、种草转化的平台&#xff01; 因为小红书是算法推荐制&#xff0c;将你的作品放在流量池中检测&#xff0c;满足要求就能进入下一个流量池…

Python酷库之旅-第三方库Pandas(061)

目录 一、用法精讲 236、pandas.Series.explode方法 236-1、语法 236-2、参数 236-3、功能 236-4、返回值 236-5、说明 236-6、用法 236-6-1、数据准备 236-6-2、代码示例 236-6-3、结果输出 237、pandas.Series.searchsorted方法 237-1、语法 237-2、参数 237-…

Linux 内核源码分析---插入和删除模块

模块是一种向 Linux 内核添加设备驱动程序、文件系统及其他组件的有效方法&#xff0c;不需编译新内核或重启系统。 模块具有如下优点&#xff1a; • 通过使用模块&#xff0c;内核发布者能够预先编译大量驱动程序&#xff0c;而不会致使内核映像的尺寸发生膨胀&#xff1b; …

PTA—基础编程题目集(7-18)

7-18 二分法求多项式单根 目录 题目描述 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 参考代码 总结 题目描述 输入在第1行中顺序给出多项式的4个系数a3​、a2​、a1​、a0​&#xff0c;在第2行中顺序给出区间端点a和b。题目保证多项式在给定区间…

60_1简单的学生管理系统【功能实现(查看所有学生(分页)、修改和删除学生信息)】

功能实现 老师角色查看所有学生 获取学生列表和分页 1.修改index.jsp 不能直接跳stuList.jsp&#xff0c;没数据 <%if("teacher".equals(role)){%><a href"TeaInitModifyServlet?username<%username%>">修改信息</a><a href…

设施农业“AutoML“时代:大模型自动调参,让农业算法模型更简单易用

&#xff08;于景鑫 北京市农林科学院智能装备技术研究中心&#xff09;设施农业是现代农业的重要发展方向,但在数字化、智能化的进程中仍面临诸多挑战。传统的农业算法模型虽然可以为设施农业提供一定的决策支持,但在实际应用中往往受限于参数调优复杂、模型泛化能力差等因素。…

<Rust><iced>基于rust使用iced构建GUI实例:一个CRC16校验码生成工具

前言 本专栏是Rust实例应用。 环境配置 平台:windows 软件:vscode 语言:rust 库:iced、iced_aw 概述 本文是专栏第五篇实例,是一个CRC16校验码转换程序。 本篇内容: 1、CRC16校验码生成 代码介绍 本文的crc16校验码生成工具,主要设计两个方面,一个是crc16 modbus…

PADS Router 扇出失败问题详细解决方法。

第一步&#xff1a;确定单位是一致的,我的单位是 “密尔”&#xff0c;不是“公制”。 第二步&#xff1a;进去pads router 右键选择特性&#xff0c;注意&#xff0c;是右键点击任意板框内空白位置的特性&#xff0c;这个是涵盖整体的设置&#xff0c;和单独点击一个元器件选…

react-native从入门到实战系列教程一Swiper组件的使用及bug修复

轮播图&#xff0c;在app中随处可见&#xff0c;这么重要的功能我们怎么可能不学习下在react-native中的实现方式。 依然是第三方组件react-native-swiper 官网地址 https://www.npmjs.com/package/react-native-swiper 组件使用的组件及事件参考官方即可。 实现效果 官网…

文件审查流程:使用指南

当您正在处理一个项目并且必须进行文档审查时&#xff0c;您可能会对这个过程到底涉及什么、谁是利益相关者以及审查过程的结果可能是什么感到困惑。在这篇博客文章中&#xff0c;让我们简单介绍一下文档审核过程及其对高质量内容的活力。 文件审查的定义 文件审查是文件经过…

CMD运行指令

CMD运行指令 开始→运行→CMD→键入以下命令即可: ASSOC显示或修改文件扩展名关联。 AT计划在计算机上运行的命令和程序。 ATTRIB显示或更改文件属性。 BREAK设置或清除扩展式CTRLC检查。 CACLS显示或修改文件的访问控制列表(ACLs)。 CALL从另一个批处理程序调用这一个。 CD显示…

不同类型游戏安全风险对抗概览(下)| FPS以及小游戏等外挂问题,一文读懂!

FPS 游戏安全问题 由于射击类游戏本身需要大量数值计算&#xff0c;游戏方会将部分计算存放于本地客户端&#xff0c;而这为外挂攻击者提供了攻击的温床。可以说&#xff0c;射击类游戏是所有游戏中被外挂攻击最为频繁的游戏类型。 根据网易易盾游戏安全部门检测数据显示&#…

未来十年机器人行业前景还好吗?

未来十年机器人行业的前景非常乐观&#xff0c;这一行业预计将持续快速发展并深刻影响我们的工作、生活和社会。以下是对未来十年机器人行业前景的详细分析&#xff1a; 一、技术驱动的创新与发展 1. 智能化与自主化&#xff1a;随着深度学习和神经网络技术的进步&#xff0c;…

MATLAB进阶:数据的拟合

几天我们继续深度学习MATLAB中的数据拟合 最小二乘拟合 假设已知经验公式yf(c,x)yf(c,x)&#xff08;c为参数&#xff0c;x为自变量&#xff09;&#xff0c;要求根据一批有误差的数据(xi,yi)&#xff0c;i0,1,...,n(xi​,yi​)&#xff0c;i0,1,...,n确定参数c。这样的问题称…

SSM大学生就业咨询管理系统-计算机毕业设计源码79442

目录 摘要 1 绪论 1.1 选题背景 1.2 研究目的和意义 1.3国内外研究现状 2系统分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1 数据流程 2.2.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2…

python合并音视频-通过moviepy模块合并音视频

&#x1f308;所属专栏&#xff1a;【python】✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的…

从零开始的MicroPython(四) 串口

上一篇&#xff1a;按键与外部中断 文章目录 前言串口&#xff08;UART&#xff09;简介MicroPython的UARTUART 类——双工串行通信 ESP32(NodeMCU-32S)GPIO简介引脚 文档代码ESP32&&PC通讯 前言 在嵌入式学习中&#xff0c;串口发挥着重要的作用&#xff0c;不仅仅是…

C++ 随机单词(萌新练习项目)

目录 项目名称 项目目标 功能描述 技术要点 示例代码 扩展建议 学习资源 项目名称 随机单词生成器&#xff08;Random Word Generator&#xff09; 项目目标 学习C基本语法和数据结构。练习使用C标准库中的随机数生成功能。理解容器&#xff08;如std::vector&#xf…

由浅入深的了解进程(5)--环境变量

环境变量 前言环境变量1、更多的环境变量及添加和删除2、整体理解环境变量系统 前言 在上一篇文章中简单的介绍了环境变量&#xff0c;但是没有讲述的比较全面了&#xff0c;所以现在再写一篇来介绍环境变量。 环境变量 在上一篇文章中&#xff0c;我们已经简单的学习了如何…

“网络身份证”来了,淘宝、微信、小红书等已上线试点版功能

“网络身份证时代”即将开启。 近日&#xff0c;公安部、国家网信办共同起草《国家网络身份认证公共服务管理办法&#xff08;征求意见稿&#xff09;》公开征求意见后&#xff0c;未来是否需要“持证上网”一时间引发热议。而在征求意见的期间&#xff0c;小红书、淘宝、微信等…