[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"site-settings":3,"\u002Farticles\u002Fdesign-pattern-04":26,"\u002Farticles\u002Fdesign-pattern-04\u002Frelated":43,"\u002Farticles\u002Fdesign-pattern-04\u002Fadjacent":72,"\u002Fcategories":76,"render-design-pattern-04":102},{"code":4,"message":5,"data":6},0,"ok",{"author_city":7,"author_years":8,"site_tagline":9,"site_description":10,"skill_one_desc":11,"author_status":12,"skill_two_title":13,"about_hero_image":9,"skill_one_title":14,"icp":15,"author_role":16,"og_default_image":9,"closing_quote_date":17,"toolkit":18,"skill_two_desc":19,"author_bio":9,"site_footer":9,"site_url":20,"author_tagline":21,"weekly_thought":9,"tagline_line_one":22,"author_bio_two":9,"closing_quote":23,"site_name":24,"tagline_line_two":25,"home_hero_image":9},"西安","10","","花开空白 - 个人博客","熟悉Java、Python、Go等后端语言。","努力工作中","前端开发","后端开发","陕ICP备2021009682号-1","开发者","2022年·秋","Java,JavaScript,TypeScript,Python,Go","熟悉Js、Ts语言以及Vue、uniapp等框架。","https:\u002F\u002Firule.cn","很高兴遇见你。","把复杂的事，","把每次一发布，都当成一次和陌生人的握手。","花开空白","写成简单的故事。",{"code":4,"message":5,"data":27},{"id":28,"slug":29,"title":30,"summary":31,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":34,"likeCount":4,"wordCount":35,"readingMinutes":36,"publishedAt":37,"contentMd":38,"contentHtml":39,"tags":40,"metaTitle":9,"metaDescription":9,"keywords":9,"canonicalUrl":9,"robots":41,"ogImage":9,"faq":42,"aiSummary":42},"2113931192066641922","design-pattern-04","设计模式修炼之路 ④ 建造者模式——告别重叠构造器","重叠构造器的坑、经典四角色建造者、Effective Java 式链式 Builder","2113897628365230081",1,3577,7441,19,"2023-11-06T18:16:44","上一篇《设计模式修炼之路-工厂模式》里，我们把\"造对象\"这件事从调用方手里收进了工厂：调用方只管报菜名，工厂负责端菜。但工厂模式有个隐藏前提——**对象在工厂里一次成型**，交付到你手上时就已经是成品了。\n\n可有一类对象天生没法\"一次成型\"：参数多、大部分可选、还想让人一眼看懂。上一篇结尾埋了个伏笔：`new Pizza(10, true, false, null, \"BIG\")`——写完这行代码三天后再回头看，谁能说清第二个 true 是芝士还是香肠？建造者模式（Builder Pattern）就是为这种场景而生的，它同样是 GoF 23 种模式之一，和工厂模式同属**创建型模式**。\n\n## 一、什么是建造者模式？\n\n先看定义：\n\n> 将一个复杂对象的**构建过程**与它的**表示**分离，使得同样的构建过程可以创建不同的表示。\n\n翻译成人话：**把\"造一个复杂对象\"拆成一步一步来，每一步的具体做法可以替换，步骤的编排由专人负责**。\n\n拿装修房子类比：你（调用方）跟装修公司说\"我要北欧风\"，设计师出图纸定流程——先水电、再瓦工、最后木工（指挥者）；工人按图纸干活（建造者），水电工瓦工木工各有班组（具体建造者）；几个月后交付的房子就是产品。换一套图纸，同一批工人能装出美式风——**流程没变，表示变了**，这就是定义后半句的意思。\n\n经典建造者模式有四个角色：\n\n- **Product（产品）**：最终要被组装出来的复杂对象；\n- **Builder（抽象建造者）**：声明组装产品的各个步骤；\n- **ConcreteBuilder（具体建造者）**：实现每个步骤的具体做法；\n- **Director（指挥者）**：编排步骤的执行顺序，对调用方屏蔽组装细节。\n\n不过先打个预防针：教科书上的四角色形态，在实际业务代码里反而不多见；你天天写的其实是它的现代变体——**链式 Builder**（《Effective Java》第 2 条力荐的写法）。两种形态本篇都会实现，先从反面教材看起。\n\n## 二、建造者模式的优缺点\n\n### 优点\n\n1. **参数可读性拉满**。每个参数都有名字，`.cheese(true)` 一眼看懂；对比 `new Pizza(12, true, false, true, \"烧烤酱\")` 里第二个 true 是什么，要靠数。\n2. **易于扩展**。新增一个可选参数，Builder 里加个方法就行，已有的调用代码一行不用改。\n3. **构建过程可复用**。经典形态里，同一个指挥者流程换个建造者就能产出不同产品；链式形态里，Builder 对象本身也能复用。\n4. **产品可以做成不可变**。字段全 final，构造完成后谁也改不了，天生线程安全。\n\n### 缺点\n\n1. **代码量几乎翻倍**。每个产品类都要配一个 Builder，样板代码不少——好在 Lombok 的 `@Builder` 注解能一键生成。\n2. **对象\"迟到\"了**。必须等 build() 调用完对象才真正存在，之前都处于\"配置阶段\"，不像构造函数一步到位。\n3. **经典形态类数量膨胀**。一种产品要配一堆具体建造者，和工厂方法一个毛病。\n\n## 三、建造者模式的应用场景\n\n建造者模式在 JDK 和主流框架里出镜率极高：\n\n- **JDK 里**：`StringBuilder` 的 `append()` 每次都返回 this，一个字符一个字符把字符串\"垒\"出来，最后 `toString()` 交付——标准的链式建造；`Stream.Builder` 更是把 `add()`\u002F`build()` 写在了脸上。\n- **Lombok 里**：`@Builder` 注解，一行注解生成整个链式 Builder，实际项目里的绝对主力。\n- **OkHttp 里**：`new Request.Builder().url(url).header(k, v).build()`，HTTP 请求的每个部件都是配出来的。\n- **Spring Security 里**：`HttpSecurity` 那一长串 `.authorizeHttpRequests(...).formLogin(...)`，把安全配置这道\"大菜\"一步步垒起来。\n\n**什么时候该用**：构造参数多（个人经验，4 个以上就该警惕）、其中大部分可选、希望产品不可变、或者同一套组装流程要产出多种\"配置\"时。反过来，一两个参数的小对象直接构造函数伺候，硬上 Builder 属于模式上瘾。\n\n## 四、建造者模式的实现方式\n\n老规矩，示例代码放在 design-pattern 项目里，本篇新增 builder 包：\n\n```\ndesign-pattern\u002Fsrc\u002Fmain\u002Fjava\n├── singleton                     \u002F\u002F 单例模式（已完结）\n├── factory                       \u002F\u002F 工厂模式（已完结）\n├── builder                       \u002F\u002F 建造者模式\n│   ├── TelescopingPizza.java     \u002F\u002F 反面教材：重叠构造器\n│   ├── Pizza.java                \u002F\u002F 链式 Builder 版披萨\n│   ├── BuilderTest.java          \u002F\u002F 测试入口\n│   └── classic                   \u002F\u002F 经典四角色\n│       ├── Meal.java             \u002F\u002F 产品：套餐\n│       ├── MealBuilder.java      \u002F\u002F 抽象建造者\n│       ├── KidsMealBuilder.java \u002F AdultMealBuilder.java   \u002F\u002F 具体建造者\n│       └── KFCWaiter.java        \u002F\u002F 指挥者\n└── prototype                     \u002F\u002F 原型模式（下一篇）\n```\n\n场景统一用快餐：先造披萨，再造套餐。\n\n### 1. 反面教材：重叠构造器\n\n参数一多，最容易写出\"重叠构造器\"（telescoping constructor）——参数个数从少到多叠一串构造函数，层层转发：\n\n```java\npublic class TelescopingPizza {\n\n    private final int size;\n    private final boolean cheese;\n    private final boolean pepperoni;\n    private final boolean mushroom;\n    private final String sauce;\n\n    public TelescopingPizza(int size) {\n        this(size, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese) {\n        this(size, cheese, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni) {\n        this(size, cheese, pepperoni, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni, boolean mushroom) {\n        this(size, cheese, pepperoni, mushroom, \"番茄酱\");\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni, boolean mushroom, String sauce) {\n        this.size = size;\n        this.cheese = cheese;\n        this.pepperoni = pepperoni;\n        this.mushroom = mushroom;\n        this.sauce = sauce;\n    }\n\n    @Override\n    public String toString() {\n        return size + \"寸披萨[酱料=\" + sauce + \", 芝士=\" + cheese + \", 香肠=\" + pepperoni + \", 蘑菇=\" + mushroom + \"]\";\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void telescoping() {\n    TelescopingPizza pizza = new TelescopingPizza(9, true, true);\n    System.out.println(\"用重叠构造器造的披萨：\" + pizza);\n}\n```\n\n运行结果：\n\n```\n用重叠构造器造的披萨：9寸披萨[酱料=番茄酱, 芝士=true, 香肠=true, 蘑菇=false]\n```\n\n能跑，但《Effective Java》直接把它列为反面教材，病根有三：\n\n1. **参数语义全靠位置硬记**。`new TelescopingPizza(9, true, true)` 里第二个 true 是芝士还是香肠？只能去数参数表，写的人在读，读的人也在数。\n2. **相邻同类型参数传反了编译器不报错**。两个 boolean 换个位置，程序照常编译照常运行，产出一份\"芝士变香肠\"的披萨，bug 能不能被发现全看运气。\n3. **中间参数跳不过去**。只想要蘑菇不想要香肠？没有 (size, cheese, mushroom) 这个构造函数，只能老老实实传 `(9, true, false, true)`，把不想要的也\"点\"一遍。\n\n有同学会说：那拆成\"无参构造 + 一堆 setter\"（JavaBeans 模式）行不行？可读性是有了，但对象从 new 出来到 setter 调完之间处于**半成品状态**，谁都能拿到引用改它两笔；而且字段没法用 final 修饰，不可变性彻底告吹。\n\n两种写法各病一头：重叠构造器可读性差但对象一次性造好，JavaBeans 可读性好但对象是\"半成品\"。建造者模式表示：两个病一起治。先看 GoF 教科书上的标准答案，再看现代的链式写法。\n\n### 2. 经典四角色建造者\n\nGoF 当年发明建造者模式时，头疼的其实不是参数太多，而是另一件事：**同一套组装流程，要能装出不同的成品**。需求场景：KFC 卖套餐，流程永远是\"主食 + 饮料\"，但儿童套餐和成人套餐的内容完全不同。\n\n先看产品，就是个简单 POJO（getter\u002Fsetter 略）：\n\n```java\npublic class Meal {\n\n    private String food;\n    private String drink;\n\n    @Override\n    public String toString() {\n        return \"套餐[主食=\" + food + \", 饮料=\" + drink + \"]\";\n    }\n}\n```\n\n抽象建造者，声明组装步骤：\n\n```java\npublic abstract class MealBuilder {\n\n    protected Meal meal = new Meal();\n\n    public abstract void buildFood();\n\n    public abstract void buildDrink();\n\n    public Meal getMeal() {\n        return meal;\n    }\n}\n```\n\n具体建造者，各自实现每一步的做法：\n\n```java\npublic class KidsMealBuilder extends MealBuilder {\n\n    @Override\n    public void buildFood() {\n        meal.setFood(\"迷你汉堡一份\");\n    }\n\n    @Override\n    public void buildDrink() {\n        meal.setDrink(\"橙汁一杯\");\n    }\n}\n```\n\nAdultMealBuilder 同理：香辣鸡腿堡一份 + 可乐一大杯。最后是指挥者，编排步骤顺序：\n\n```java\npublic class KFCWaiter {\n\n    private MealBuilder mealBuilder;\n\n    public void setMealBuilder(MealBuilder mealBuilder) {\n        this.mealBuilder = mealBuilder;\n    }\n\n    public Meal construct() {\n        mealBuilder.buildFood();\n        mealBuilder.buildDrink();\n        return mealBuilder.getMeal();\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void classicBuilder() {\n    KFCWaiter waiter = new KFCWaiter();\n    waiter.setMealBuilder(new KidsMealBuilder());\n    Meal kidsMeal = waiter.construct();\n    System.out.println(kidsMeal);\n    waiter.setMealBuilder(new AdultMealBuilder());\n    Meal adultMeal = waiter.construct();\n    System.out.println(adultMeal);\n}\n```\n\n运行结果：\n\n```\n套餐[主食=迷你汉堡一份, 饮料=橙汁一杯]\n套餐[主食=香辣鸡腿堡一份, 饮料=可乐一大杯]\n```\n\n对着四角色过一遍：Meal 是产品，MealBuilder 是抽象建造者，KidsMealBuilder\u002FAdultMealBuilder 是具体建造者，KFCWaiter 是指挥者。注意调用方全程只跟 waiter 打交道，**连 Meal 的 set 方法都没碰过**——\"怎么造\"被彻底封装；而 waiter 持有的是抽象的 MealBuilder，换一个具体建造者，同一套 construct() 流程就端出了完全不同的套餐——\"同样的构建过程，不同的表示\"，定义就这么活了。\n\n不过经典形态解决的是\"流程复用\"，对第一小节\"参数爆炸\"的三个病并没有直接治。日常开发里真正天天在用的，是下面这一版。\n\n### 3. 链式 Builder（Effective Java 式）\n\n现代建造者把指挥者这个角色砍了——**调用方自己当指挥**，想配什么配什么，配完喊一声 build()：\n\n```java\npublic class Pizza {\n\n    private final int size;\n    private final boolean cheese;\n    private final boolean pepperoni;\n    private final boolean mushroom;\n    private final String sauce;\n\n    private Pizza(Builder builder) {\n        this.size = builder.size;\n        this.cheese = builder.cheese;\n        this.pepperoni = builder.pepperoni;\n        this.mushroom = builder.mushroom;\n        this.sauce = builder.sauce;\n    }\n\n    @Override\n    public String toString() {\n        return size + \"寸披萨[酱料=\" + sauce + \", 芝士=\" + cheese + \", 香肠=\" + pepperoni + \", 蘑菇=\" + mushroom + \"]\";\n    }\n\n    public static class Builder {\n\n        private final int size; \u002F\u002F 必选参数，final 保证必须在构造时传入\n        private boolean cheese;\n        private boolean pepperoni;\n        private boolean mushroom;\n        private String sauce = \"番茄酱\"; \u002F\u002F 可选参数，给默认值\n\n        public Builder(int size) {\n            this.size = size;\n        }\n\n        public Builder cheese(boolean value) {\n            this.cheese = value;\n            return this;\n        }\n\n        public Builder pepperoni(boolean value) {\n            this.pepperoni = value;\n            return this;\n        }\n\n        public Builder mushroom(boolean value) {\n            this.mushroom = value;\n            return this;\n        }\n\n        public Builder sauce(String value) {\n            this.sauce = value;\n            return this;\n        }\n\n        public Pizza build() {\n            return new Pizza(this);\n        }\n    }\n}\n```\n\n测试代码：\n\n```java\nprivate static void fluentBuilder() {\n    Pizza pizza = new Pizza.Builder(12)\n            .cheese(true)\n            .mushroom(true)\n            .sauce(\"烧烤酱\")\n            .build();\n    System.out.println(\"用链式Builder造的披萨：\" + pizza);\n}\n```\n\n运行结果：\n\n```\n用链式Builder造的披萨：12寸披萨[酱料=烧烤酱, 芝士=true, 香肠=false, 蘑菇=true]\n```\n\n逐个拆解设计意图：\n\n- **必选参数收进 Builder 的构造函数**，还声明成 final——不传尺寸连 Builder 都拿不到，\"必选项忘传\"这种事故从根上杜绝；\n- **可选参数给默认值**，想配哪个点哪个，重叠构造器\"中间参数跳不过去\"的病治好了；\n- **每个配置方法都 return this**，于是可以一路点下去，这就是\"链式\"；每个参数前面都有方法名，两个 boolean 换了位置代码直接不编译，\"同类型参数传反\"的病也治好了；\n- **Pizza 的构造函数是 private 的**，外界只能通过 build() 拿到成品；配置阶段你手里握的是 Builder，半成品状态暴露不出去，build() 完事的那一刻对象一次性定型——JavaBeans\"半成品\"的病同样治好了；\n- **字段全部 final**，造出来就是只读的，天生线程安全。\n\n要是还想加参数校验，往 build() 里塞：尺寸不合法、酱料为空，直接抛 IllegalArgumentException，\"不合格产品不出厂\"。\n\n实际项目里，这一套样板代码基本都交给 Lombok 的 @Builder 生成，但手写过一遍，你才知道注解背后到底发生了什么。\n\n## 五、工厂模式和建造者模式怎么选\n\n同为创建型模式，俩人常被搞混，其实分工清清楚楚：\n\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>\u003C\u002Fth>\u003Cth>工厂模式\u003C\u002Fth>\u003Cth>建造者模式\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>关注点\u003C\u002Ftd>\u003Ctd>造**什么**（产品选型）\u003C\u002Ftd>\u003Ctd>**怎么**造（组装配置）\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>对象成型时机\u003C\u002Ftd>\u003Ctd>工厂一次成型\u003C\u002Ftd>\u003Ctd>分步配置，build() 时成型\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>擅长场景\u003C\u002Ftd>\u003Ctd>同族产品按需创建\u003C\u002Ftd>\u003Ctd>多参数复杂对象的精细配置\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>典型 API\u003C\u002Ftd>\u003Ctd>`ShapeFactory.getShape(\"CIRCLE\")`\u003C\u002Ftd>\u003Ctd>`new Pizza.Builder(12).cheese(true).build()`\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>一句话\u003C\u002Ftd>\u003Ctd>报菜名，直接上菜\u003C\u002Ftd>\u003Ctd>报需求，现场拼装\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\n\n选型建议同样朴素：**参数少而固定，直接构造函数；重点在\"选哪个\"，用工厂；重点在\"怎么配\"，用建造者**。两者的关注点是正交的，组合使用也很常见——工厂负责选型，Builder 负责按需精装，很多框架的配置类就是这么干的。\n\n## 写在最后\n\n回顾这一篇：重叠构造器参数靠位置硬记、同类型参数传反不报错、中间参数跳不过去；经典四角色用\"产品、抽象建造者、具体建造者、指挥者\"把组装流程和具体做法分离，同一套流程装出不同的成品；链式 Builder 砍掉指挥者、调用方亲自上阵，必选参数进构造函数、可选参数带名字链式配置、build() 一次成型，三个老病全治好。日常开发里，Lombok @Builder 一键生成的大概率就是这最后一版。\n\n示例代码都在 design-pattern 项目的 builder 包里（classic 子包是经典四角色），和前几篇的代码同处一个项目，方便对照练习。\n\n创建型模式还剩最后一位选手：**原型模式（Prototype）**——当\"造一个对象\"的成本太高时，最经济的办法往往是：找一个现成的，复制一份。克隆羊多利的故事，下一篇见。","\u003Cp>上一篇《设计模式修炼之路-工厂模式》里，我们把\"造对象\"这件事从调用方手里收进了工厂：调用方只管报菜名，工厂负责端菜。但工厂模式有个隐藏前提——\u003Cstrong>对象在工厂里一次成型\u003C\u002Fstrong>，交付到你手上时就已经是成品了。\u003C\u002Fp>\n\u003Cp>可有一类对象天生没法\"一次成型\"：参数多、大部分可选、还想让人一眼看懂。上一篇结尾埋了个伏笔：\u003Ccode>new Pizza(10, true, false, null, \"BIG\")\u003C\u002Fcode>——写完这行代码三天后再回头看，谁能说清第二个 true 是芝士还是香肠？建造者模式（Builder Pattern）就是为这种场景而生的，它同样是 GoF 23 种模式之一，和工厂模式同属\u003Cstrong>创建型模式\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch2>一、什么是建造者模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>将一个复杂对象的\u003Cstrong>构建过程\u003C\u002Fstrong>与它的\u003Cstrong>表示\u003C\u002Fstrong>分离，使得同样的构建过程可以创建不同的表示。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把\"造一个复杂对象\"拆成一步一步来，每一步的具体做法可以替换，步骤的编排由专人负责\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>拿装修房子类比：你（调用方）跟装修公司说\"我要北欧风\"，设计师出图纸定流程——先水电、再瓦工、最后木工（指挥者）；工人按图纸干活（建造者），水电工瓦工木工各有班组（具体建造者）；几个月后交付的房子就是产品。换一套图纸，同一批工人能装出美式风——\u003Cstrong>流程没变，表示变了\u003C\u002Fstrong>，这就是定义后半句的意思。\u003C\u002Fp>\n\u003Cp>经典建造者模式有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Product（产品）\u003C\u002Fstrong>：最终要被组装出来的复杂对象；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Builder（抽象建造者）\u003C\u002Fstrong>：声明组装产品的各个步骤；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteBuilder（具体建造者）\u003C\u002Fstrong>：实现每个步骤的具体做法；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Director（指挥者）\u003C\u002Fstrong>：编排步骤的执行顺序，对调用方屏蔽组装细节。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>不过先打个预防针：教科书上的四角色形态，在实际业务代码里反而不多见；你天天写的其实是它的现代变体——\u003Cstrong>链式 Builder\u003C\u002Fstrong>（《Effective Java》第 2 条力荐的写法）。两种形态本篇都会实现，先从反面教材看起。\u003C\u002Fp>\n\u003Ch2>二、建造者模式的优缺点\u003C\u002Fh2>\n\u003Ch3>优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>参数可读性拉满\u003C\u002Fstrong>。每个参数都有名字，\u003Ccode>.cheese(true)\u003C\u002Fcode> 一眼看懂；对比 \u003Ccode>new Pizza(12, true, false, true, \"烧烤酱\")\u003C\u002Fcode> 里第二个 true 是什么，要靠数。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>易于扩展\u003C\u002Fstrong>。新增一个可选参数，Builder 里加个方法就行，已有的调用代码一行不用改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>构建过程可复用\u003C\u002Fstrong>。经典形态里，同一个指挥者流程换个建造者就能产出不同产品；链式形态里，Builder 对象本身也能复用。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>产品可以做成不可变\u003C\u002Fstrong>。字段全 final，构造完成后谁也改不了，天生线程安全。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>代码量几乎翻倍\u003C\u002Fstrong>。每个产品类都要配一个 Builder，样板代码不少——好在 Lombok 的 \u003Ccode>@Builder\u003C\u002Fcode> 注解能一键生成。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象\"迟到\"了\u003C\u002Fstrong>。必须等 build() 调用完对象才真正存在，之前都处于\"配置阶段\"，不像构造函数一步到位。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>经典形态类数量膨胀\u003C\u002Fstrong>。一种产品要配一堆具体建造者，和工厂方法一个毛病。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2>三、建造者模式的应用场景\u003C\u002Fh2>\n\u003Cp>建造者模式在 JDK 和主流框架里出镜率极高：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK 里\u003C\u002Fstrong>：\u003Ccode>StringBuilder\u003C\u002Fcode> 的 \u003Ccode>append()\u003C\u002Fcode> 每次都返回 this，一个字符一个字符把字符串\"垒\"出来，最后 \u003Ccode>toString()\u003C\u002Fcode> 交付——标准的链式建造；\u003Ccode>Stream.Builder\u003C\u002Fcode> 更是把 \u003Ccode>add()\u003C\u002Fcode>\u002F\u003Ccode>build()\u003C\u002Fcode> 写在了脸上。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Lombok 里\u003C\u002Fstrong>：\u003Ccode>@Builder\u003C\u002Fcode> 注解，一行注解生成整个链式 Builder，实际项目里的绝对主力。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>OkHttp 里\u003C\u002Fstrong>：\u003Ccode>new Request.Builder().url(url).header(k, v).build()\u003C\u002Fcode>，HTTP 请求的每个部件都是配出来的。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring Security 里\u003C\u002Fstrong>：\u003Ccode>HttpSecurity\u003C\u002Fcode> 那一长串 \u003Ccode>.authorizeHttpRequests(...).formLogin(...)\u003C\u002Fcode>，把安全配置这道\"大菜\"一步步垒起来。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：构造参数多（个人经验，4 个以上就该警惕）、其中大部分可选、希望产品不可变、或者同一套组装流程要产出多种\"配置\"时。反过来，一两个参数的小对象直接构造函数伺候，硬上 Builder 属于模式上瘾。\u003C\u002Fp>\n\u003Ch2>四、建造者模式的实现方式\u003C\u002Fh2>\n\u003Cp>老规矩，示例代码放在 design-pattern 项目里，本篇新增 builder 包：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>design-pattern\u002Fsrc\u002Fmain\u002Fjava\n├── singleton                     \u002F\u002F 单例模式（已完结）\n├── factory                       \u002F\u002F 工厂模式（已完结）\n├── builder                       \u002F\u002F 建造者模式\n│   ├── TelescopingPizza.java     \u002F\u002F 反面教材：重叠构造器\n│   ├── Pizza.java                \u002F\u002F 链式 Builder 版披萨\n│   ├── BuilderTest.java          \u002F\u002F 测试入口\n│   └── classic                   \u002F\u002F 经典四角色\n│       ├── Meal.java             \u002F\u002F 产品：套餐\n│       ├── MealBuilder.java      \u002F\u002F 抽象建造者\n│       ├── KidsMealBuilder.java \u002F AdultMealBuilder.java   \u002F\u002F 具体建造者\n│       └── KFCWaiter.java        \u002F\u002F 指挥者\n└── prototype                     \u002F\u002F 原型模式（下一篇）\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景统一用快餐：先造披萨，再造套餐。\u003C\u002Fp>\n\u003Ch3>1. 反面教材：重叠构造器\u003C\u002Fh3>\n\u003Cp>参数一多，最容易写出\"重叠构造器\"（telescoping constructor）——参数个数从少到多叠一串构造函数，层层转发：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class TelescopingPizza {\n\n    private final int size;\n    private final boolean cheese;\n    private final boolean pepperoni;\n    private final boolean mushroom;\n    private final String sauce;\n\n    public TelescopingPizza(int size) {\n        this(size, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese) {\n        this(size, cheese, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni) {\n        this(size, cheese, pepperoni, false);\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni, boolean mushroom) {\n        this(size, cheese, pepperoni, mushroom, \"番茄酱\");\n    }\n\n    public TelescopingPizza(int size, boolean cheese, boolean pepperoni, boolean mushroom, String sauce) {\n        this.size = size;\n        this.cheese = cheese;\n        this.pepperoni = pepperoni;\n        this.mushroom = mushroom;\n        this.sauce = sauce;\n    }\n\n    @Override\n    public String toString() {\n        return size + \"寸披萨[酱料=\" + sauce + \", 芝士=\" + cheese + \", 香肠=\" + pepperoni + \", 蘑菇=\" + mushroom + \"]\";\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void telescoping() {\n    TelescopingPizza pizza = new TelescopingPizza(9, true, true);\n    System.out.println(\"用重叠构造器造的披萨：\" + pizza);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>用重叠构造器造的披萨：9寸披萨[酱料=番茄酱, 芝士=true, 香肠=true, 蘑菇=false]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但《Effective Java》直接把它列为反面教材，病根有三：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>参数语义全靠位置硬记\u003C\u002Fstrong>。\u003Ccode>new TelescopingPizza(9, true, true)\u003C\u002Fcode> 里第二个 true 是芝士还是香肠？只能去数参数表，写的人在读，读的人也在数。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>相邻同类型参数传反了编译器不报错\u003C\u002Fstrong>。两个 boolean 换个位置，程序照常编译照常运行，产出一份\"芝士变香肠\"的披萨，bug 能不能被发现全看运气。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中间参数跳不过去\u003C\u002Fstrong>。只想要蘑菇不想要香肠？没有 (size, cheese, mushroom) 这个构造函数，只能老老实实传 \u003Ccode>(9, true, false, true)\u003C\u002Fcode>，把不想要的也\"点\"一遍。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>有同学会说：那拆成\"无参构造 + 一堆 setter\"（JavaBeans 模式）行不行？可读性是有了，但对象从 new 出来到 setter 调完之间处于\u003Cstrong>半成品状态\u003C\u002Fstrong>，谁都能拿到引用改它两笔；而且字段没法用 final 修饰，不可变性彻底告吹。\u003C\u002Fp>\n\u003Cp>两种写法各病一头：重叠构造器可读性差但对象一次性造好，JavaBeans 可读性好但对象是\"半成品\"。建造者模式表示：两个病一起治。先看 GoF 教科书上的标准答案，再看现代的链式写法。\u003C\u002Fp>\n\u003Ch3>2. 经典四角色建造者\u003C\u002Fh3>\n\u003Cp>GoF 当年发明建造者模式时，头疼的其实不是参数太多，而是另一件事：\u003Cstrong>同一套组装流程，要能装出不同的成品\u003C\u002Fstrong>。需求场景：KFC 卖套餐，流程永远是\"主食 + 饮料\"，但儿童套餐和成人套餐的内容完全不同。\u003C\u002Fp>\n\u003Cp>先看产品，就是个简单 POJO（getter\u002Fsetter 略）：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Meal {\n\n    private String food;\n    private String drink;\n\n    @Override\n    public String toString() {\n        return \"套餐[主食=\" + food + \", 饮料=\" + drink + \"]\";\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>抽象建造者，声明组装步骤：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public abstract class MealBuilder {\n\n    protected Meal meal = new Meal();\n\n    public abstract void buildFood();\n\n    public abstract void buildDrink();\n\n    public Meal getMeal() {\n        return meal;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体建造者，各自实现每一步的做法：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class KidsMealBuilder extends MealBuilder {\n\n    @Override\n    public void buildFood() {\n        meal.setFood(\"迷你汉堡一份\");\n    }\n\n    @Override\n    public void buildDrink() {\n        meal.setDrink(\"橙汁一杯\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>AdultMealBuilder 同理：香辣鸡腿堡一份 + 可乐一大杯。最后是指挥者，编排步骤顺序：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class KFCWaiter {\n\n    private MealBuilder mealBuilder;\n\n    public void setMealBuilder(MealBuilder mealBuilder) {\n        this.mealBuilder = mealBuilder;\n    }\n\n    public Meal construct() {\n        mealBuilder.buildFood();\n        mealBuilder.buildDrink();\n        return mealBuilder.getMeal();\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void classicBuilder() {\n    KFCWaiter waiter = new KFCWaiter();\n    waiter.setMealBuilder(new KidsMealBuilder());\n    Meal kidsMeal = waiter.construct();\n    System.out.println(kidsMeal);\n    waiter.setMealBuilder(new AdultMealBuilder());\n    Meal adultMeal = waiter.construct();\n    System.out.println(adultMeal);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>套餐[主食=迷你汉堡一份, 饮料=橙汁一杯]\n套餐[主食=香辣鸡腿堡一份, 饮料=可乐一大杯]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对着四角色过一遍：Meal 是产品，MealBuilder 是抽象建造者，KidsMealBuilder\u002FAdultMealBuilder 是具体建造者，KFCWaiter 是指挥者。注意调用方全程只跟 waiter 打交道，\u003Cstrong>连 Meal 的 set 方法都没碰过\u003C\u002Fstrong>——\"怎么造\"被彻底封装；而 waiter 持有的是抽象的 MealBuilder，换一个具体建造者，同一套 construct() 流程就端出了完全不同的套餐——\"同样的构建过程，不同的表示\"，定义就这么活了。\u003C\u002Fp>\n\u003Cp>不过经典形态解决的是\"流程复用\"，对第一小节\"参数爆炸\"的三个病并没有直接治。日常开发里真正天天在用的，是下面这一版。\u003C\u002Fp>\n\u003Ch3>3. 链式 Builder（Effective Java 式）\u003C\u002Fh3>\n\u003Cp>现代建造者把指挥者这个角色砍了——\u003Cstrong>调用方自己当指挥\u003C\u002Fstrong>，想配什么配什么，配完喊一声 build()：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">public class Pizza {\n\n    private final int size;\n    private final boolean cheese;\n    private final boolean pepperoni;\n    private final boolean mushroom;\n    private final String sauce;\n\n    private Pizza(Builder builder) {\n        this.size = builder.size;\n        this.cheese = builder.cheese;\n        this.pepperoni = builder.pepperoni;\n        this.mushroom = builder.mushroom;\n        this.sauce = builder.sauce;\n    }\n\n    @Override\n    public String toString() {\n        return size + \"寸披萨[酱料=\" + sauce + \", 芝士=\" + cheese + \", 香肠=\" + pepperoni + \", 蘑菇=\" + mushroom + \"]\";\n    }\n\n    public static class Builder {\n\n        private final int size; \u002F\u002F 必选参数，final 保证必须在构造时传入\n        private boolean cheese;\n        private boolean pepperoni;\n        private boolean mushroom;\n        private String sauce = \"番茄酱\"; \u002F\u002F 可选参数，给默认值\n\n        public Builder(int size) {\n            this.size = size;\n        }\n\n        public Builder cheese(boolean value) {\n            this.cheese = value;\n            return this;\n        }\n\n        public Builder pepperoni(boolean value) {\n            this.pepperoni = value;\n            return this;\n        }\n\n        public Builder mushroom(boolean value) {\n            this.mushroom = value;\n            return this;\n        }\n\n        public Builder sauce(String value) {\n            this.sauce = value;\n            return this;\n        }\n\n        public Pizza build() {\n            return new Pizza(this);\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-java\">private static void fluentBuilder() {\n    Pizza pizza = new Pizza.Builder(12)\n            .cheese(true)\n            .mushroom(true)\n            .sauce(\"烧烤酱\")\n            .build();\n    System.out.println(\"用链式Builder造的披萨：\" + pizza);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>用链式Builder造的披萨：12寸披萨[酱料=烧烤酱, 芝士=true, 香肠=false, 蘑菇=true]\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>逐个拆解设计意图：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>必选参数收进 Builder 的构造函数\u003C\u002Fstrong>，还声明成 final——不传尺寸连 Builder 都拿不到，\"必选项忘传\"这种事故从根上杜绝；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>可选参数给默认值\u003C\u002Fstrong>，想配哪个点哪个，重叠构造器\"中间参数跳不过去\"的病治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每个配置方法都 return this\u003C\u002Fstrong>，于是可以一路点下去，这就是\"链式\"；每个参数前面都有方法名，两个 boolean 换了位置代码直接不编译，\"同类型参数传反\"的病也治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Pizza 的构造函数是 private 的\u003C\u002Fstrong>，外界只能通过 build() 拿到成品；配置阶段你手里握的是 Builder，半成品状态暴露不出去，build() 完事的那一刻对象一次性定型——JavaBeans\"半成品\"的病同样治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>字段全部 final\u003C\u002Fstrong>，造出来就是只读的，天生线程安全。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>要是还想加参数校验，往 build() 里塞：尺寸不合法、酱料为空，直接抛 IllegalArgumentException，\"不合格产品不出厂\"。\u003C\u002Fp>\n\u003Cp>实际项目里，这一套样板代码基本都交给 Lombok 的 @Builder 生成，但手写过一遍，你才知道注解背后到底发生了什么。\u003C\u002Fp>\n\u003Ch2>五、工厂模式和建造者模式怎么选\u003C\u002Fh2>\n\u003Cp>同为创建型模式，俩人常被搞混，其实分工清清楚楚：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>工厂模式\u003C\u002Fth>\n   \u003Cth>建造者模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>关注点\u003C\u002Ftd>\n   \u003Ctd>造**什么**（产品选型）\u003C\u002Ftd>\n   \u003Ctd>**怎么**造（组装配置）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>对象成型时机\u003C\u002Ftd>\n   \u003Ctd>工厂一次成型\u003C\u002Ftd>\n   \u003Ctd>分步配置，build() 时成型\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>擅长场景\u003C\u002Ftd>\n   \u003Ctd>同族产品按需创建\u003C\u002Ftd>\n   \u003Ctd>多参数复杂对象的精细配置\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型 API\u003C\u002Ftd>\n   \u003Ctd>`ShapeFactory.getShape(\"CIRCLE\")`\u003C\u002Ftd>\n   \u003Ctd>`new Pizza.Builder(12).cheese(true).build()`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>一句话\u003C\u002Ftd>\n   \u003Ctd>报菜名，直接上菜\u003C\u002Ftd>\n   \u003Ctd>报需求，现场拼装\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>选型建议同样朴素：\u003Cstrong>参数少而固定，直接构造函数；重点在\"选哪个\"，用工厂；重点在\"怎么配\"，用建造者\u003C\u002Fstrong>。两者的关注点是正交的，组合使用也很常见——工厂负责选型，Builder 负责按需精装，很多框架的配置类就是这么干的。\u003C\u002Fp>\n\u003Ch2>写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：重叠构造器参数靠位置硬记、同类型参数传反不报错、中间参数跳不过去；经典四角色用\"产品、抽象建造者、具体建造者、指挥者\"把组装流程和具体做法分离，同一套流程装出不同的成品；链式 Builder 砍掉指挥者、调用方亲自上阵，必选参数进构造函数、可选参数带名字链式配置、build() 一次成型，三个老病全治好。日常开发里，Lombok @Builder 一键生成的大概率就是这最后一版。\u003C\u002Fp>\n\u003Cp>示例代码都在 design-pattern 项目的 builder 包里（classic 子包是经典四角色），和前几篇的代码同处一个项目，方便对照练习。\u003C\u002Fp>\n\u003Cp>创建型模式还剩最后一位选手：\u003Cstrong>原型模式（Prototype）\u003C\u002Fstrong>——当\"造一个对象\"的成本太高时，最经济的办法往往是：找一个现成的，复制一份。克隆羊多利的故事，下一篇见。\u003C\u002Fp>",[],"index,follow",null,{"code":4,"message":5,"data":44},[45,54,63],{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},"2113931131085656066","design-pattern-03","设计模式修炼之路 ③ 工厂模式——简单工厂、工厂方法、抽象工厂三兄弟","简单工厂、工厂方法、抽象工厂三兄弟的演进与取舍、开闭原则得失",3741,6415,17,"2023-10-13T21:36:05",{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},"2113931247385317377","design-pattern-05","设计模式修炼之路 ⑤ 原型模式——clone() 的浅与深","clone() 与 Cloneable、浅拷贝踩坑、逐字段克隆与序列化两种深拷贝",2661,5758,15,"2023-11-17T17:12:14",{"id":64,"slug":65,"title":66,"summary":67,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":68,"likeCount":4,"wordCount":69,"readingMinutes":70,"publishedAt":71},"2113932265082851329","design-pattern-18","设计模式修炼之路 ⑱ 命令模式——把请求封装成对象","把请求封装成对象、增量式撤销、白送的排队与重放、Executor 的铁证",2089,7080,18,"2024-08-09T12:36:13",{"code":4,"message":5,"data":73},{"prev":74,"next":75},{"id":46,"slug":47,"title":48,"summary":49,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":50,"likeCount":4,"wordCount":51,"readingMinutes":52,"publishedAt":53},{"id":55,"slug":56,"title":57,"summary":58,"coverUrl":9,"categoryId":32,"status":33,"topFlag":4,"viewCount":59,"likeCount":4,"wordCount":60,"readingMinutes":61,"publishedAt":62},{"code":4,"message":5,"data":77},[78,84,89,95],{"id":79,"name":80,"slug":81,"description":82,"sortOrder":4,"articleCount":83},"1946000000000000001","antlr4","tech","编程与技术笔记","13",{"id":32,"name":85,"slug":86,"description":87,"sortOrder":4,"articleCount":88},"设计模式","design-pattern","设计模式系列","34",{"id":90,"name":91,"slug":92,"description":93,"sortOrder":33,"articleCount":94},"1946000000000000002","随笔","essay","生活与思考","0",{"id":96,"name":97,"slug":98,"description":99,"sortOrder":100,"articleCount":101},"2105200093764698113","工具","tools","好用的工具推荐",2,"1",{"html":103,"toc":104},"\u003Cp>上一篇《设计模式修炼之路-工厂模式》里，我们把\"造对象\"这件事从调用方手里收进了工厂：调用方只管报菜名，工厂负责端菜。但工厂模式有个隐藏前提——\u003Cstrong>对象在工厂里一次成型\u003C\u002Fstrong>，交付到你手上时就已经是成品了。\u003C\u002Fp>\n\u003Cp>可有一类对象天生没法\"一次成型\"：参数多、大部分可选、还想让人一眼看懂。上一篇结尾埋了个伏笔：\u003Ccode>new Pizza(10, true, false, null, \"BIG\")\u003C\u002Fcode>——写完这行代码三天后再回头看，谁能说清第二个 true 是芝士还是香肠？建造者模式（Builder Pattern）就是为这种场景而生的，它同样是 GoF 23 种模式之一，和工厂模式同属\u003Cstrong>创建型模式\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch2 id=\"toc-1\">一、什么是建造者模式？\u003C\u002Fh2>\n\u003Cp>先看定义：\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>将一个复杂对象的\u003Cstrong>构建过程\u003C\u002Fstrong>与它的\u003Cstrong>表示\u003C\u002Fstrong>分离，使得同样的构建过程可以创建不同的表示。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>翻译成人话：\u003Cstrong>把\"造一个复杂对象\"拆成一步一步来，每一步的具体做法可以替换，步骤的编排由专人负责\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>拿装修房子类比：你（调用方）跟装修公司说\"我要北欧风\"，设计师出图纸定流程——先水电、再瓦工、最后木工（指挥者）；工人按图纸干活（建造者），水电工瓦工木工各有班组（具体建造者）；几个月后交付的房子就是产品。换一套图纸，同一批工人能装出美式风——\u003Cstrong>流程没变，表示变了\u003C\u002Fstrong>，这就是定义后半句的意思。\u003C\u002Fp>\n\u003Cp>经典建造者模式有四个角色：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>Product（产品）\u003C\u002Fstrong>：最终要被组装出来的复杂对象；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Builder（抽象建造者）\u003C\u002Fstrong>：声明组装产品的各个步骤；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>ConcreteBuilder（具体建造者）\u003C\u002Fstrong>：实现每个步骤的具体做法；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Director（指挥者）\u003C\u002Fstrong>：编排步骤的执行顺序，对调用方屏蔽组装细节。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>不过先打个预防针：教科书上的四角色形态，在实际业务代码里反而不多见；你天天写的其实是它的现代变体——\u003Cstrong>链式 Builder\u003C\u002Fstrong>（《Effective Java》第 2 条力荐的写法）。两种形态本篇都会实现，先从反面教材看起。\u003C\u002Fp>\n\u003Ch2 id=\"toc-2\">二、建造者模式的优缺点\u003C\u002Fh2>\n\u003Ch3 id=\"toc-3\">优点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>参数可读性拉满\u003C\u002Fstrong>。每个参数都有名字，\u003Ccode>.cheese(true)\u003C\u002Fcode> 一眼看懂；对比 \u003Ccode>new Pizza(12, true, false, true, \"烧烤酱\")\u003C\u002Fcode> 里第二个 true 是什么，要靠数。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>易于扩展\u003C\u002Fstrong>。新增一个可选参数，Builder 里加个方法就行，已有的调用代码一行不用改。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>构建过程可复用\u003C\u002Fstrong>。经典形态里，同一个指挥者流程换个建造者就能产出不同产品；链式形态里，Builder 对象本身也能复用。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>产品可以做成不可变\u003C\u002Fstrong>。字段全 final，构造完成后谁也改不了，天生线程安全。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3 id=\"toc-4\">缺点\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>代码量几乎翻倍\u003C\u002Fstrong>。每个产品类都要配一个 Builder，样板代码不少——好在 Lombok 的 \u003Ccode>@Builder\u003C\u002Fcode> 注解能一键生成。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>对象\"迟到\"了\u003C\u002Fstrong>。必须等 build() 调用完对象才真正存在，之前都处于\"配置阶段\"，不像构造函数一步到位。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>经典形态类数量膨胀\u003C\u002Fstrong>。一种产品要配一堆具体建造者，和工厂方法一个毛病。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"toc-5\">三、建造者模式的应用场景\u003C\u002Fh2>\n\u003Cp>建造者模式在 JDK 和主流框架里出镜率极高：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>JDK 里\u003C\u002Fstrong>：\u003Ccode>StringBuilder\u003C\u002Fcode> 的 \u003Ccode>append()\u003C\u002Fcode> 每次都返回 this，一个字符一个字符把字符串\"垒\"出来，最后 \u003Ccode>toString()\u003C\u002Fcode> 交付——标准的链式建造；\u003Ccode>Stream.Builder\u003C\u002Fcode> 更是把 \u003Ccode>add()\u003C\u002Fcode>\u002F\u003Ccode>build()\u003C\u002Fcode> 写在了脸上。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Lombok 里\u003C\u002Fstrong>：\u003Ccode>@Builder\u003C\u002Fcode> 注解，一行注解生成整个链式 Builder，实际项目里的绝对主力。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>OkHttp 里\u003C\u002Fstrong>：\u003Ccode>new Request.Builder().url(url).header(k, v).build()\u003C\u002Fcode>，HTTP 请求的每个部件都是配出来的。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Spring Security 里\u003C\u002Fstrong>：\u003Ccode>HttpSecurity\u003C\u002Fcode> 那一长串 \u003Ccode>.authorizeHttpRequests(...).formLogin(...)\u003C\u002Fcode>，把安全配置这道\"大菜\"一步步垒起来。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>什么时候该用\u003C\u002Fstrong>：构造参数多（个人经验，4 个以上就该警惕）、其中大部分可选、希望产品不可变、或者同一套组装流程要产出多种\"配置\"时。反过来，一两个参数的小对象直接构造函数伺候，硬上 Builder 属于模式上瘾。\u003C\u002Fp>\n\u003Ch2 id=\"toc-6\">四、建造者模式的实现方式\u003C\u002Fh2>\n\u003Cp>老规矩，示例代码放在 design-pattern 项目里，本篇新增 builder 包：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>design-pattern\u002Fsrc\u002Fmain\u002Fjava\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>├── singleton                     \u002F\u002F 单例模式（已完结）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>├── factory                       \u002F\u002F 工厂模式（已完结）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>├── builder                       \u002F\u002F 建造者模式\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│   ├── TelescopingPizza.java     \u002F\u002F 反面教材：重叠构造器\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│   ├── Pizza.java                \u002F\u002F 链式 Builder 版披萨\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│   ├── BuilderTest.java          \u002F\u002F 测试入口\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│   └── classic                   \u002F\u002F 经典四角色\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│       ├── Meal.java             \u002F\u002F 产品：套餐\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│       ├── MealBuilder.java      \u002F\u002F 抽象建造者\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│       ├── KidsMealBuilder.java \u002F AdultMealBuilder.java   \u002F\u002F 具体建造者\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>│       └── KFCWaiter.java        \u002F\u002F 指挥者\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>└── prototype                     \u002F\u002F 原型模式（下一篇）\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>场景统一用快餐：先造披萨，再造套餐。\u003C\u002Fp>\n\u003Ch3 id=\"toc-7\">1. 反面教材：重叠构造器\u003C\u002Fh3>\n\u003Cp>参数一多，最容易写出\"重叠构造器\"（telescoping constructor）——参数个数从少到多叠一串构造函数，层层转发：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String sauce;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(size, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">false\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(size, cheese, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">false\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> pepperoni\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(size, cheese, pepperoni, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">false\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> pepperoni\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> mushroom\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(size, cheese, pepperoni, mushroom, \u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"番茄酱\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> pepperoni\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> mushroom\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">sauce\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.size \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.cheese \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.pepperoni \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mushroom \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> sauce;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"寸披萨[酱料=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 芝士=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 香肠=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 蘑菇=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> telescoping\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    TelescopingPizza pizza \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> TelescopingPizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">9\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用重叠构造器造的披萨：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pizza);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>用重叠构造器造的披萨：9寸披萨[酱料=番茄酱, 芝士=true, 香肠=true, 蘑菇=false]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>能跑，但《Effective Java》直接把它列为反面教材，病根有三：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>参数语义全靠位置硬记\u003C\u002Fstrong>。\u003Ccode>new TelescopingPizza(9, true, true)\u003C\u002Fcode> 里第二个 true 是芝士还是香肠？只能去数参数表，写的人在读，读的人也在数。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>相邻同类型参数传反了编译器不报错\u003C\u002Fstrong>。两个 boolean 换个位置，程序照常编译照常运行，产出一份\"芝士变香肠\"的披萨，bug 能不能被发现全看运气。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>中间参数跳不过去\u003C\u002Fstrong>。只想要蘑菇不想要香肠？没有 (size, cheese, mushroom) 这个构造函数，只能老老实实传 \u003Ccode>(9, true, false, true)\u003C\u002Fcode>，把不想要的也\"点\"一遍。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>有同学会说：那拆成\"无参构造 + 一堆 setter\"（JavaBeans 模式）行不行？可读性是有了，但对象从 new 出来到 setter 调完之间处于\u003Cstrong>半成品状态\u003C\u002Fstrong>，谁都能拿到引用改它两笔；而且字段没法用 final 修饰，不可变性彻底告吹。\u003C\u002Fp>\n\u003Cp>两种写法各病一头：重叠构造器可读性差但对象一次性造好，JavaBeans 可读性好但对象是\"半成品\"。建造者模式表示：两个病一起治。先看 GoF 教科书上的标准答案，再看现代的链式写法。\u003C\u002Fp>\n\u003Ch3 id=\"toc-8\">2. 经典四角色建造者\u003C\u002Fh3>\n\u003Cp>GoF 当年发明建造者模式时，头疼的其实不是参数太多，而是另一件事：\u003Cstrong>同一套组装流程，要能装出不同的成品\u003C\u002Fstrong>。需求场景：KFC 卖套餐，流程永远是\"主食 + 饮料\"，但儿童套餐和成人套餐的内容完全不同。\u003C\u002Fp>\n\u003Cp>先看产品，就是个简单 POJO（getter\u002Fsetter 略）：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Meal\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String food;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String drink;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"套餐[主食=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> food \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 饮料=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> drink \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>抽象建造者，声明组装步骤：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    protected\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Meal meal \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Meal\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> buildFood\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> abstract\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> buildDrink\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Meal \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getMeal\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> meal;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>具体建造者，各自实现每一步的做法：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> KidsMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> extends\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> MealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> buildFood\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        meal.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setFood\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"迷你汉堡一份\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> buildDrink\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        meal.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setDrink\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"橙汁一杯\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>AdultMealBuilder 同理：香辣鸡腿堡一份 + 可乐一大杯。最后是指挥者，编排步骤顺序：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> KFCWaiter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> MealBuilder mealBuilder;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> setMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(MealBuilder \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">mealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mealBuilder \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mealBuilder;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Meal \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">construct\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mealBuilder.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">buildFood\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        mealBuilder.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">buildDrink\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mealBuilder.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">getMeal\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> classicBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    KFCWaiter waiter \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> KFCWaiter\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    waiter.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> KidsMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Meal kidsMeal \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> waiter.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">construct\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(kidsMeal);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    waiter.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">setMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> AdultMealBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">());\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Meal adultMeal \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> waiter.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">construct\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(adultMeal);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>套餐[主食=迷你汉堡一份, 饮料=橙汁一杯]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>套餐[主食=香辣鸡腿堡一份, 饮料=可乐一大杯]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>对着四角色过一遍：Meal 是产品，MealBuilder 是抽象建造者，KidsMealBuilder\u002FAdultMealBuilder 是具体建造者，KFCWaiter 是指挥者。注意调用方全程只跟 waiter 打交道，\u003Cstrong>连 Meal 的 set 方法都没碰过\u003C\u002Fstrong>——\"怎么造\"被彻底封装；而 waiter 持有的是抽象的 MealBuilder，换一个具体建造者，同一套 construct() 流程就端出了完全不同的套餐——\"同样的构建过程，不同的表示\"，定义就这么活了。\u003C\u002Fp>\n\u003Cp>不过经典形态解决的是\"流程复用\"，对第一小节\"参数爆炸\"的三个病并没有直接治。日常开发里真正天天在用的，是下面这一版。\u003C\u002Fp>\n\u003Ch3 id=\"toc-9\">3. 链式 Builder（Effective Java 式）\u003C\u002Fh3>\n\u003Cp>现代建造者把指挥者这个角色砍了——\u003Cstrong>调用方自己当指挥\u003C\u002Fstrong>，想配什么配什么，配完喊一声 build()：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Pizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String sauce;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    private\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Pizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(Builder \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">builder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.size \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder.size;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.cheese \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder.cheese;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.pepperoni \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder.pepperoni;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mushroom \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder.mushroom;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">        this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> builder.sauce;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    @\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">Override\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">toString\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"寸披萨[酱料=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 芝士=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 香肠=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \", 蘑菇=\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"]\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">    public\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> class\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Builder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> final\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> int\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 必选参数，final 保证必须在构造时传入\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> cheese;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pepperoni;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> boolean\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mushroom;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        private\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> String sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\"> \"番茄酱\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">; \u003C\u002Fspan>\u003Cspan style=\"color:#6A737D;--shiki-dark:#6A737D\">\u002F\u002F 可选参数，给默认值\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Builder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">int\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> size\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.size \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> size;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Builder \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.cheese \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Builder \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">pepperoni\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.pepperoni \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Builder \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">mushroom\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">boolean\u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\"> value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mushroom \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Builder \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sauce\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(String \u003C\u002Fspan>\u003Cspan style=\"color:#E36209;--shiki-dark:#FFAB70\">value\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">) {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">            this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">.sauce \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> value;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\"> this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">;\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">        public\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Pizza \u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">build\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">            return\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Pizza\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">this\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">        }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    }\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>测试代码：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">private\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> static\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> void\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\"> fluentBuilder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">() {\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    Pizza pizza \u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> new\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> Pizza.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">Builder\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">12\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">cheese\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">mushroom\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#005CC5;--shiki-dark:#79B8FF\">true\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">sauce\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"烧烤酱\"\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">            .\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">build\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">();\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">    System.out.\u003C\u002Fspan>\u003Cspan style=\"color:#6F42C1;--shiki-dark:#B392F0\">println\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#032F62;--shiki-dark:#9ECBFF\">\"用链式Builder造的披萨：\"\u003C\u002Fspan>\u003Cspan style=\"color:#D73A49;--shiki-dark:#F97583\"> +\u003C\u002Fspan>\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pizza);\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#24292E;--shiki-dark:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>运行结果：\u003C\u002Fp>\n\u003Cpre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan>用链式Builder造的披萨：12寸披萨[酱料=烧烤酱, 芝士=true, 香肠=false, 蘑菇=true]\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>逐个拆解设计意图：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>必选参数收进 Builder 的构造函数\u003C\u002Fstrong>，还声明成 final——不传尺寸连 Builder 都拿不到，\"必选项忘传\"这种事故从根上杜绝；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>可选参数给默认值\u003C\u002Fstrong>，想配哪个点哪个，重叠构造器\"中间参数跳不过去\"的病治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>每个配置方法都 return this\u003C\u002Fstrong>，于是可以一路点下去，这就是\"链式\"；每个参数前面都有方法名，两个 boolean 换了位置代码直接不编译，\"同类型参数传反\"的病也治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>Pizza 的构造函数是 private 的\u003C\u002Fstrong>，外界只能通过 build() 拿到成品；配置阶段你手里握的是 Builder，半成品状态暴露不出去，build() 完事的那一刻对象一次性定型——JavaBeans\"半成品\"的病同样治好了；\u003C\u002Fli>\n \u003Cli>\u003Cstrong>字段全部 final\u003C\u002Fstrong>，造出来就是只读的，天生线程安全。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>要是还想加参数校验，往 build() 里塞：尺寸不合法、酱料为空，直接抛 IllegalArgumentException，\"不合格产品不出厂\"。\u003C\u002Fp>\n\u003Cp>实际项目里，这一套样板代码基本都交给 Lombok 的 @Builder 生成，但手写过一遍，你才知道注解背后到底发生了什么。\u003C\u002Fp>\n\u003Ch2 id=\"toc-10\">五、工厂模式和建造者模式怎么选\u003C\u002Fh2>\n\u003Cp>同为创建型模式，俩人常被搞混，其实分工清清楚楚：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>\u003C\u002Fth>\n   \u003Cth>工厂模式\u003C\u002Fth>\n   \u003Cth>建造者模式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>关注点\u003C\u002Ftd>\n   \u003Ctd>造**什么**（产品选型）\u003C\u002Ftd>\n   \u003Ctd>**怎么**造（组装配置）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>对象成型时机\u003C\u002Ftd>\n   \u003Ctd>工厂一次成型\u003C\u002Ftd>\n   \u003Ctd>分步配置，build() 时成型\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>擅长场景\u003C\u002Ftd>\n   \u003Ctd>同族产品按需创建\u003C\u002Ftd>\n   \u003Ctd>多参数复杂对象的精细配置\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>典型 API\u003C\u002Ftd>\n   \u003Ctd>`ShapeFactory.getShape(\"CIRCLE\")`\u003C\u002Ftd>\n   \u003Ctd>`new Pizza.Builder(12).cheese(true).build()`\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>一句话\u003C\u002Ftd>\n   \u003Ctd>报菜名，直接上菜\u003C\u002Ftd>\n   \u003Ctd>报需求，现场拼装\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>选型建议同样朴素：\u003Cstrong>参数少而固定，直接构造函数；重点在\"选哪个\"，用工厂；重点在\"怎么配\"，用建造者\u003C\u002Fstrong>。两者的关注点是正交的，组合使用也很常见——工厂负责选型，Builder 负责按需精装，很多框架的配置类就是这么干的。\u003C\u002Fp>\n\u003Ch2 id=\"toc-11\">写在最后\u003C\u002Fh2>\n\u003Cp>回顾这一篇：重叠构造器参数靠位置硬记、同类型参数传反不报错、中间参数跳不过去；经典四角色用\"产品、抽象建造者、具体建造者、指挥者\"把组装流程和具体做法分离，同一套流程装出不同的成品；链式 Builder 砍掉指挥者、调用方亲自上阵，必选参数进构造函数、可选参数带名字链式配置、build() 一次成型，三个老病全治好。日常开发里，Lombok @Builder 一键生成的大概率就是这最后一版。\u003C\u002Fp>\n\u003Cp>示例代码都在 design-pattern 项目的 builder 包里（classic 子包是经典四角色），和前几篇的代码同处一个项目，方便对照练习。\u003C\u002Fp>\n\u003Cp>创建型模式还剩最后一位选手：\u003Cstrong>原型模式（Prototype）\u003C\u002Fstrong>——当\"造一个对象\"的成本太高时，最经济的办法往往是：找一个现成的，复制一份。克隆羊多利的故事，下一篇见。\u003C\u002Fp>",[105,108,111,115,118,121,124,127,130,133,136],{"id":106,"text":107,"level":100},"toc-1","一、什么是建造者模式？",{"id":109,"text":110,"level":100},"toc-2","二、建造者模式的优缺点",{"id":112,"text":113,"level":114},"toc-3","优点",3,{"id":116,"text":117,"level":114},"toc-4","缺点",{"id":119,"text":120,"level":100},"toc-5","三、建造者模式的应用场景",{"id":122,"text":123,"level":100},"toc-6","四、建造者模式的实现方式",{"id":125,"text":126,"level":114},"toc-7","1. 反面教材：重叠构造器",{"id":128,"text":129,"level":114},"toc-8","2. 经典四角色建造者",{"id":131,"text":132,"level":114},"toc-9","3. 链式 Builder（Effective Java 式）",{"id":134,"text":135,"level":100},"toc-10","五、工厂模式和建造者模式怎么选",{"id":137,"text":138,"level":100},"toc-11","写在最后"]