生成器办法

制作者办法Builder

生成器办法是一种创立型规划办法, 使你能够分进程创立杂乱目标。 该办法允许你运用相同的创立代码生成不同类型和办法的目标。

遇到的问题

假设有这样一个杂乱目标, 在对其进行结构时需求对诸多成员变量和嵌套目标进行繁复的初始化作业。 这些初始化代码通常深藏于一个包含很多参数且让人根本看不懂的结构函数中; 乃至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。

2023 跟我一起学设计模式:生成器模式

假如为每种或许的目标都创立一个子类, 这或许会导致程序变得过于杂乱。

例如, 咱们来考虑怎么创立一个 房子House目标。 制作一栋简单的房子, 首要你需求制作四面墙和地板, 安装房门和一套窗户, 然后再制作一个房顶。 可是假如你想要一栋更宽阔更亮堂的房子, 还要有宅院和其他设备 (例如暖气、 排水和供电设备), 那又该怎么办呢?

最简单的办法是扩展 房子基类, 然后创立一系列包括一切参数组合的子类。 但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加杂乱。

另一种办法则无需生成子类。 你能够在 房子基类中创立一个包括一切或许参数的超级结构函数, 并用它来操控房子目标。 这种办法的确能够防止生成子类, 但它却会形成别的一个问题。

2023 跟我一起学设计模式:生成器模式

拥有很多输入参数的结构函数也有缺陷: 这些参数也不是每次都要全部用上的。

解决方案

生成器办法建议将目标结构代码从产品类中抽取出来, 并将其放在一个名为生成器的独立目标中。

2023 跟我一起学设计模式:生成器模式

生成器办法让你能够分进程创立杂乱目标。 生成器不允许其他目标拜访正在创立中的产品。

该办法会将目标结构进程划分为一组进程, 比方 buildWalls创立墙面和 buildDoor创立房门创立房门等。 每次创立目标时, 你都需求经过生成器目标履行一系列进程。 要点在于你无需调用一切进程, 而只需调用创立特定目标装备所需的那些进程即可。

当你需求创立不同办法的产品时, 其中的一些结构进程或许需求不同的完结。 例如, 木屋的房门或许需求运用木头制作, 而城堡的房门则必须运用石头制作。

在这种情况下, 你能够创立多个不同的生成器, 用不同办法完结一组相同的创立进程。 然后你就能够在创立进程中运用这些生成器 (例如按次序调用多个结构进程) 来生成不同类型的目标。

2023 跟我一起学设计模式:生成器模式

不同生成器以不同办法履行相同的任务。

例如, 假设第一个制作者运用木头和玻璃制作房子, 第二个制作者运用石头和钢铁, 而第三个制作者运用黄金和钻石。 在调用同一组进程后, 第一个制作者会给你一栋普通房子, 第二个会给你一座小城堡, 而第三个则会给你一座宫廷。 可是, 只要在调用结构进程的客户端代码能够经过通用接口与制作者进行交互时, 这样的调用才干回来需求的房子。

生成器办法结构

2023 跟我一起学设计模式:生成器模式

  1. 生成器 (Builder) 接口声明在一切类型生成器中通用的产品结构进程。
  2. 详细生成器 (Concrete Builders) 供给结构进程的不同完结。 详细生成器也能够结构不遵循通用接口的产品。
  3. 产品 (Products) 是最终生成的目标。 由不同生成器结构的产品无需归于同一类层次结构或接口。
  4. 主管 (Director) 类定义调用结构进程的次序, 这样你就能够创立和复用特定的产品装备。
  5. 客户端 (Client) 必须将某个生成器目标与主管类相关。 一般情况下, 你只需经过主管类结构函数的参数进行一次性相关即可。 此后主管类就能运用生成器目标完结后续一切的结构任务。 但在客户端将生成器目标传递给主管类制作办法时还有另一种办法。 在这种情况下, 你在运用主管类出产产品时每次都能够运用不同的生成器。

伪代码

下面关于生成器办法的例子演示了你能够怎么复用相同的目标结构代码来生成不同类型的产品——例如轿车 (Car)——及其相应的运用手册 (Manual)。

2023 跟我一起学设计模式:生成器模式

分进程制作轿车并制作对应类型用户运用手册的示例

轿车是一个杂乱目标, 有数百种不同的制作办法。 咱们没有在 轿车类中塞入一个巨型结构函数, 而是将轿车拼装代码抽取到独自的轿车生成器类中。 该类中有一组办法可用来装备轿车的各种部件。

假如客户端代码需求拼装一辆异乎寻常、 精心调教的轿车, 它能够直接调用生成器。 或许, 客户端能够将拼装作业委托给主管类, 由于主管类知道怎么运用生成器制作最受欢迎的几种类型轿车。

你或许会感到吃惊, 但的确每辆轿车都需求一本运用手册 (说真的, 谁会去读它们呢?)。 运用手册会介绍轿车的每一项功用, 因而不同类型的轿车, 其运用手册内容也不一样。 因而, 你能够复用现有流程来制作实践的轿车及其对应的手册。 当然, 编写手册和制作轿车不是一回事, 所以咱们需求别的一个生成器目标来专门编写运用手册。 该类与其制作轿车的兄弟类都完结了相同的制作办法, 可是其功用不是制作轿车部件, 而是描绘每个部件。 将这些生成器传递给相同的主管目标, 咱们就能够生成一辆轿车或是一本运用手册了。

最后一个部分是获取成果目标。 虽然金属轿车和纸质手册存在相关, 但它们却是完全不同的东西。 咱们无法在主管类和详细产品类不发生耦合的情况下, 在主管类中供给获取成果目标的办法。 因而, 咱们只能经过担任制作进程的生成器来获取成果目标。

生成器办法合适运用场景

运用生成器办法可防止 “重叠结构函数 (telescoping constructor)” 的出现。

假设你的结构函数中有十个可选参数, 那么调用该函数会十分不便利; 因而, 你需求重载这个结构函数, 新建几个只要较少参数的简化版。 但这些结构函数仍需调用主结构函数, 传递一些默认数值来代替省略掉的参数。

只要在 C# 或 Java 等支持办法重载的编程语言中才干写出如此杂乱的结构函数。

生成器办法让你能够分进程生成目标, 而且允许你仅运用必须的进程。 运用该办法后, 你再也不需求将几十个参数塞进结构函数里了。

当你希望运用代码创立不同办法的产品 (例如石头或木头房子) 时, 可运用生成器办法。

假如你需求创立的各种办法的产品, 它们的制作进程类似且仅有细节上的差异, 此刻可运用生成器办法。

根本生成器接口中定义了一切或许的制作进程, 详细生成器将完结这些进程来制作特定办法的产品。 同时, 主管类将担任办理制作进程的次序。

运用生成器结构[组合]树或其他杂乱目标。

生成器办法让你能分进程结构产品。 你能够延迟履行某些进程而不会影响最终产品。 你乃至能够递归调用这些进程, 这在创立目标树时十分便利。

生成器在履行制作进程时, 不能对外发布未完结的产品。 这能够防止客户端代码获取到不完整成果目标的情况。

完结办法

  1. 清晰地定义通用进程, 确保它们能够制作一切办法的产品。 否则你将无法进一步实施该办法。

  2. 在根本生成器接口中声明这些进程。

  3. 为每个办法的产品创立详细生成器类, 并完结其结构进程。

    不要忘掉完结获取结构成果目标的办法。 你不能在生成器接口中声明该办法, 由于不同生成器结构的产品或许没有公共接口, 因而你就不知道该办法回来的目标类型。 可是, 假如一切产品都位于单一类层次中, 你就能够安全地在根本接口中添加获取生成目标的办法。

  4. 考虑创立主管类。 它能够运用同一生成器目标来封装多种结构产品的办法。

  5. 客户端代码会同时创立生成器和主管目标。 结构开端前, 客户端必须将生成器目标传递给主管目标。 通常情况下, 客户端只需调用主管类结构函数一次即可。 主管类运用生成器目标完结后续一切制作任务。 还有另一种办法, 那就是客户端能够将生成器目标直接传递给主管类的制作办法。

  6. 只要在一切产品都遵循相同接口的情况下, 结构成果能够直接经过主管类获取。 否则, 客户端应当经过生成器获取结构成果。

生成器办法优缺点

  • 你能够分步创立目标, 暂缓创立进程或递归运转创立进程。

  • 生成不同办法的产品时, 你能够复用相同的制作代码。

  • 单一职责准则。 你能够将杂乱结构代码从产品的事务逻辑中分离出来。

  • 由于该办法需求新增多个类, 因而代码整体杂乱程度会有所增加。

代码示例

Golang 生成器办法解说和代码示例 – 掘金 ()