作者:鱼宵 | 「Java 后端实战精通营」设计模式系列・第 3 篇
一、真实场景:上次说"第 3 课用工厂收拾它",这周该兑现了
系列第 1 篇,我们给一份故意写烂的外卖订单代码做了体检:一个 processOrder 方法干了 5 件事,最扎眼的就是 if (order.type == 1) ... else if (order.type == 2) ... 这段分发。当时我在文末留了句预告------第 3 课用工厂消灭魔法数字。中间第 2 篇讲了单例:一个类自己控制实例,全宇宙只有一个。今天这课刚好反过来------把"new 谁"这件事从业务代码里赶出去,交给专门的工厂。
先回到那个痛点。老板又要加一种订单类型:"拼团单"。你打开 processOrder,在库存那段的 if-else 里又塞一个分支:
java
if (order.type == 1) { /* 普通单 */ }
else if (order.type == 2) { /* 预售单 */ }
else if (order.type == 3) { /* 团购单 */ }
else if (order.type == 4) { /* 新加的拼团单 */ }
怕的不是写这三行,是这三件事:
type == 4这个 4 是什么意思?三个月后你自己都看不懂------魔法数字;- 调用方那边可能还写着
new NormalOrderHandler()这种具体类------你想换个实现,得回来改业务代码; - 万一上游传了个
type == 9的脏数据,else分支打印一句话就溜了,订单丢了没人知道------静默失败。
这三件事,就是今天工厂模式要一起收拾的。代码全在 design-patterns 仓库的 lesson-03 工程里,clone 下来跑一遍,改前改后的对比当场就出来。
二、核心原理:工厂模式到底把 "new" 收到哪去了?
2.1 先记住一句话:new 就是硬编码
业务代码里写 new NormalOrderHandler(),意味着它和"普通单处理器"这个具体类焊死了,想换实现就得回来改代码。工厂模式干的事就一句话:把"创建哪个对象"的决策,从业务代码里抽出来,集中交给一个工厂。业务代码只跟接口打交道,不关心背后 new 的是哪个类。
类比:你点外卖只跟 App 说"我要一份黄焖鸡",你要的是"一份能吃的饭"(接口),不关心是哪家店做的。换一家店,你的点餐流程一行没变------工厂就是中间那个"帮你找到对的店"的角色。
2.2 三兄弟:简单工厂、工厂方法、抽象工厂
面试最爱考"三者区别",先上三个生活类比:
- 简单工厂 = 一个全能师傅开的小店。你说要椅子他打椅子,说要桌子他打桌子,店里就他一个人,一个静态方法搞定所有产品。代码最简单,但加新产品得回来改这个师傅的手工台。
- 工厂方法 = 师傅们各自开店。椅子师傅只打椅子、桌子师傅只打桌子。你要椅子就去椅子店------加新产品 = 新开一家店,老店一个字不动。
- 抽象工厂 = 风格套餐工厂。"北欧风工厂"出的椅子桌子灯具全是北欧风,"中式风工厂"出的全是中式。你绝不会从北欧工厂买到一把太师椅------产品族不串味。
2.3 对比表(面试直接背)
| 简单工厂 | 工厂方法 | 抽象工厂 | |
|---|---|---|---|
| 一个工厂造什么 | 一个方法造所有产品 | 一个工厂只造一个产品 | 一个工厂造一族配套产品 |
| 加新产品怎么办 | 改工厂里的 switch | 新增一个工厂类 | 改工厂接口 + 所有实现类 |
| 生活类比 | 全能师傅小店 | 一门一个专人 | 风格套餐工厂 |
| 算 GoF 正式模式吗 | ❌ 不算(编程习惯) | ✅ 算 | ✅ 算 |
最后一行是面试冷知识:简单工厂不是 GoF 23 种模式之一,它是工厂方法的阉割简化版。面试官问"简单工厂算设计模式吗",答"不算,它是工厂方法的简化写法",加分。
2.4 选型口诀(一句话)
换地区、换套餐要整套切换 → 抽象工厂;产品种类会越来越多、每种规则独立 → 工厂方法;产品就两三种、基本不变 → 简单工厂足够,别过度设计。
三、关键代码:给 type if-else 开药方
本课工程一共 17 个类,全在 lesson-03/src/main/java/com/dp/lesson03/ 下。药方分三步:枚举消灭魔法数字 → 接口抽象产品 → 工厂集中创建。一步一步来。
3.1 第一步:OrderType 枚举,给魔法数字上户口
java
package com.dp.lesson03;
/**
* 订单类型枚举:第 1 课坏代码里 type=1/2/3 魔法数字的"解药"。
*/
public enum OrderType {
/** 普通单:立即扣库存 */
NORMAL(1, "普通单"),
/** 预售单:先锁库存,到期再扣 */
PRESALE(2, "预售单"),
/** 团购单:凑够人数批量扣 */
GROUP(3, "团购单");
/** 类型编号:对外协议/数据库里可能还在用数字,枚举里留一个 code 做翻译 */
private final int code;
/** 中文描述:打印日志时人看得懂 */
private final String desc;
OrderType(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
/**
* 按 code 反查枚举:查不到直接抛异常,把问题暴露在第一现场。
* 第 1 课坏代码的 else 分支只是"打印一句话"------这里不认识就当场炸。
*/
public static OrderType fromCode(int code) {
for (OrderType type : values()) {
if (type.code == code) {
return type;
}
}
// 未知 code 一律报错,不允许"不认识也放行"
throw new IllegalArgumentException("未知订单类型 code=" + code
+ ":请先在 OrderType 里登记,再写对应的处理器");
}
}
这段代码讲了两件事:一是 OrderType.PRESALE 比裸写 2 清楚十倍,参数声明成枚举后,谁随便传个 99 编译都过不了;二是 fromCode() 查不到直接抛异常------对比第 1 课 else 分支"打印一句话继续跑",未知类型从"悄悄丢单"变成"当场炸出来"。
3.2 第二步:OrderHandler 接口,每种类型一个实现类
java
package com.dp.lesson03;
/** 订单处理器接口(工厂模式里的"产品"抽象):每种类型自己实现怎么处理。 */
public interface OrderHandler {
/** 处理一个订单(这里只演库存环节,其他环节后面的课再拆) */
void handle(Order order);
}
java
package com.dp.lesson03;
/** 普通单处理器(具体产品 1):买家付款后立即扣减库存。 */
public class NormalOrderHandler implements OrderHandler {
@Override
public void handle(Order order) {
System.out.println("[库存] 普通单 " + order.getOrderId() + ":立即扣减库存");
}
}
预售单、团购单两个实现是同一个套路:PresaleOrderHandler 打印"只锁库存,不扣减",GroupOrderHandler 打印"按团购人数批量扣减",完整源码在 lesson-03 工程里。注意接口只留一个 handle 方法------接口隔离原则,别搞个塞四五个方法的上帝接口。加新订单类型 = 新写一个实现类,老的三个类一行不动。
配套的 Order 类也从第 1 课的 public 裸奔改成 private 字段 + getter(最少知识法则),type 字段直接换成枚举:
java
package com.dp.lesson03;
/** 订单对象:第 1 课 Order 的"重构版"。 */
public class Order {
/** 订单号 */
private final String orderId;
/** 订单类型:枚举,不再是 int */
private final OrderType type;
/** 金额(元) */
private final double amount;
public Order(String orderId, OrderType type, double amount) {
this.orderId = orderId;
this.type = type;
this.amount = amount;
}
public String getOrderId() {
return orderId;
}
public OrderType getType() {
return type;
}
public double getAmount() {
return amount;
}
@Override
public String toString() {
return String.format("订单[%s] 类型=%s(%d) 金额=%.2f",
orderId, type.getDesc(), type.getCode(), amount);
}
}
3.3 第三步:SimpleOrderFactory,把 switch 收进一个静态方法
java
package com.dp.lesson03;
/** 简单工厂:一个静态方法,按类型返回对应处理器。它不是 GoF 23 种模式之一。 */
public final class SimpleOrderFactory {
private SimpleOrderFactory() {
// 纯静态工具类,禁止实例化
}
/** 按订单类型创建对应的处理器。 */
public static OrderHandler createHandler(OrderType type) {
switch (type) {
case NORMAL:
return new NormalOrderHandler();
case PRESALE:
return new PresaleOrderHandler();
case GROUP:
return new GroupOrderHandler();
default:
// 加了新枚举值却没加分支?立刻报错,不让它静默穿透
throw new IllegalStateException(
"OrderType 新增了枚举值,但工厂没有对应分支:" + type);
}
}
}
业务代码从此只剩一行:OrderHandler h = SimpleOrderFactory.createHandler(type);------没有 new、没有 if-else。重点看 default:Java 对枚举 switch 不强制穷举,哪天有人加了新枚举值却忘了加 case,编译照样过;但跑到 default 当场炸------把"漏改"从线上 bug 变成运行期第一现场。
3.4 工厂方法版:创建者也抽象一层
简单工厂的短板是:加新产品还得回来改这个 switch。产品种类暴涨怎么办?把"创建谁"推迟给子类------每个产品配一个小工厂:
java
package com.dp.lesson03;
/** 工厂方法的"创建者"抽象类:产品由子类决定,业务流程是固定的。 */
public abstract class OrderHandlerFactory {
/** 工厂方法:子类决定到底 new 哪个处理器,本类不关心。 */
public abstract OrderHandler createHandler();
/** 业务流程:固定骨架------先拿产品,再让产品干活。 */
public void process(Order order) {
OrderHandler handler = createHandler();
System.out.println("[工厂方法] 使用处理器:" + handler.getClass().getSimpleName());
handler.handle(order);
}
}
java
package com.dp.lesson03;
/** 具体工厂 1:只负责造"普通单处理器"。预售/团购工厂同款(lesson-03 源码里)。 */
public class NormalOrderHandlerFactory extends OrderHandlerFactory {
@Override
public OrderHandler createHandler() {
return new NormalOrderHandler();
}
}
注意抽象类里有两类方法:抽象的 createHandler()(造产品,子类填)和具体的 process()(业务流程,固定)。创建是弹性的,流程是固定的------这就是工厂方法的精髓。代价也直白:每种产品多一个小类,Normal、Presale、Group 三个具体工厂,代码短得像废话,但这就是开闭原则的门票钱。
3.5 抽象工厂版:国内 / 海外两族配套组件
工厂方法扩展"产品种类"自由,但有一种场景它搞不定:业务要整套切换。国内下单:人民币校验 + 短信通知;海外下单:美元限额 + 英文邮件。你绝不会想混出一个"海外校验规则 + 国内短信"的怪胎。抽象工厂就是管这个的:
java
package com.dp.lesson03;
/** 订单校验器(抽象工厂的产品 A):校验不通过直接抛异常。 */
public interface OrderValidator {
void validate(Order order);
}
java
package com.dp.lesson03;
/** 订单通知器(抽象工厂的产品 B):按对应渠道通知用户。 */
public interface OrderNotifier {
void notify(Order order);
}
java
package com.dp.lesson03;
/** 抽象工厂接口:一个工厂负责生产"一族配套"组件。 */
public interface OrderComponentFactory {
/** 本族配套的校验器 */
OrderValidator createValidator();
/** 本族配套的通知器 */
OrderNotifier createNotifier();
}
java
package com.dp.lesson03;
/** 国内族工厂:金额大于 0 就过 + 发短信。 */
public class DomesticOrderComponentFactory implements OrderComponentFactory {
@Override
public OrderValidator createValidator() {
return order -> {
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("[国内校验] 金额必须大于 0");
}
System.out.println("[国内校验] " + order.getOrderId()
+ " 金额" + order.getAmount() + " 元,校验通过");
};
}
@Override
public OrderNotifier createNotifier() {
return order -> System.out.println(
"[国内通知] 给用户发短信:您的订单 " + order.getOrderId() + " 已受理");
}
}
java
package com.dp.lesson03;
/** 海外族工厂:单笔限额 10000 + 发英文邮件。 */
public class OverseasOrderComponentFactory implements OrderComponentFactory {
@Override
public OrderValidator createValidator() {
return order -> {
// 海外单笔限额 10000:业务规则和国内不一样
if (order.getAmount() > 10000) {
throw new IllegalArgumentException("[海外校验] 单笔金额超过 10000 限额");
}
System.out.println("[海外校验] " + order.getOrderId()
+ " 金额" + order.getAmount() + " 美元,符合海外限额");
};
}
@Override
public OrderNotifier createNotifier() {
return order -> System.out.println(
"[海外通知] 发英文邮件:Your order " + order.getOrderId() + " is confirmed");
}
}
业务代码里切换整套方案,就把 new DomesticOrderComponentFactory() 换成 new OverseasOrderComponentFactory(),后面两个 create 方法自动跟着整套换,互不串味。代价也记牢:抽象工厂对"加产品族"开放,对"加产品种类"不开放------哪天要加个"开票器",接口和两个实现类都得动。
3.6 Main:把三种工厂串起来跑一遍
java
package com.dp.lesson03;
import java.util.List;
/** 第 3 课入口:把第 1 课"订单类型 if-else 分发"用三种工厂重构跑一遍。 */
public class Main {
public static void main(String[] args) {
System.out.println("========== 第 3 课:工厂模式重构订单类型分发 ==========");
// ---------- 0. 改前:第 1 课坏代码的样子(打印出来做对照) ----------
System.out.println("【改前】第 1 课 BadCodeOrderService 里的分发逻辑:");
System.out.println(" if (order.type == 1) { 普通单:立即扣库存 }");
System.out.println(" else if (order.type == 2) { 预售单:只锁库存 }");
System.out.println(" else if (order.type == 3) { 团购单:批量扣减 }");
System.out.println(" else { 打印一句'未知类型',不报错------静默失败! }");
System.out.println("痛点:加第 4 种类型要回来改老方法;魔法数字没人看得懂。");
System.out.println();
// ---------- 1. 改后第一步:枚举 + 简单工厂分发 ----------
System.out.println("---------- 改后:枚举 + 简单工厂 ----------");
int[] codes = {1, 2, 3};
for (int code : codes) {
// fromCode 把数字翻译成枚举:含义自解释,查不到直接抛异常
OrderType type = OrderType.fromCode(code);
Order order = new Order("A200" + code, type, 100.0 * code);
System.out.println("订单:" + order);
// 业务代码只剩这一行:不关心背后 new 的是谁
OrderHandler handler = SimpleOrderFactory.createHandler(type);
handler.handle(order);
}
System.out.println();
// ---------- 2. 未知类型:对比第 1 课的静默失败 ----------
System.out.println("---------- 未知类型 code=9 怎么办 ----------");
try {
OrderType.fromCode(9);
} catch (IllegalArgumentException e) {
System.out.println("未知类型被拦下:" + e.getMessage());
System.out.println("对比第 1 课:以前是打印一句话继续跑,订单直接丢失没人知道。");
}
System.out.println();
// ---------- 3. 工厂方法:每个产品配一个具体工厂 ----------
System.out.println("---------- 工厂方法:一门一个专人 ----------");
List<OrderHandlerFactory> factories = List.of(
new NormalOrderHandlerFactory(),
new PresaleOrderHandlerFactory(),
new GroupOrderHandlerFactory()
);
Order orderNormal = new Order("B3001", OrderType.NORMAL, 99.0);
Order orderPresale = new Order("B3002", OrderType.PRESALE, 520.0);
Order orderGroup = new Order("B3003", OrderType.GROUP, 1800.0);
Order[] orders = {orderNormal, orderPresale, orderGroup};
for (int i = 0; i < factories.size(); i++) {
// 抽象创建者 process 固定流程:内部自己 createHandler,调用方不 new 任何产品
factories.get(i).process(orders[i]);
}
System.out.println();
// ---------- 4. 抽象工厂:国内/海外两族配套组件 ----------
System.out.println("---------- 抽象工厂:产品族不串味 ----------");
// 国内族:国内校验 + 国内短信
OrderComponentFactory domestic = new DomesticOrderComponentFactory();
Order domesticOrder = new Order("C4001", OrderType.NORMAL, 399.0);
domestic.createValidator().validate(domesticOrder);
domestic.createNotifier().notify(domesticOrder);
System.out.println();
// 海外族:海外限额校验 + 英文邮件。业务代码只换这一行工厂
OrderComponentFactory overseas = new OverseasOrderComponentFactory();
Order overseasOrder = new Order("C4002", OrderType.NORMAL, 888.0);
overseas.createValidator().validate(overseasOrder);
overseas.createNotifier().notify(overseasOrder);
System.out.println();
// 海外族的限额规则演示:15000 超限额,校验直接抛异常
try {
Order bigOrder = new Order("C4003", OrderType.NORMAL, 15000.0);
overseas.createValidator().validate(bigOrder);
} catch (IllegalArgumentException e) {
System.out.println("海外大额单被拦下:" + e.getMessage());
}
System.out.println();
System.out.println("========== 结束:新增订单类型 = 加一个枚举值 + 加一个处理器类,老代码一行不改 ==========");
}
}
这段 Main 讲了什么:先把第 1 课的坏代码打印出来当对照,然后依次跑简单工厂分发三单、未知类型 code=9 被拦、工厂方法三单、抽象工厂国内海外两族------后面贴的实测输出就是这份代码原样跑出来的。
四、完整演示 + 实测输出
clone 下来后,在 lesson-03 目录执行(Windows + PowerShell,没有 bash):
powershell
cd C:\Users\haoji\Desktop\课程\design-patterns-journey\lesson-03
$env:JAVA_HOME="C:\jdk\jdk-17.0.12"
mvn clean install -DskipTests
编译实测输出:
[INFO] BUILD SUCCESS
[INFO] Total time: 1.348 s
编译通过后运行(用 JDK17 全路径,并先切中文控制台编码):
powershell
[Console]::OutputEncoding=[System.Text.Encoding]::GetEncoding(936)
& "C:\jdk\jdk-17.0.12\bin\java.exe" -cp ".\target\classes" com.dp.lesson03.Main
实测输出(本机真实跑出来的):
========== 第 3 课:工厂模式重构订单类型分发 ==========
【改前】第 1 课 BadCodeOrderService 里的分发逻辑:
if (order.type == 1) { 普通单:立即扣库存 }
else if (order.type == 2) { 预售单:只锁库存 }
else if (order.type == 3) { 团购单:批量扣减 }
else { 打印一句'未知类型',不报错------静默失败! }
痛点:加第 4 种类型要回来改老方法;魔法数字没人看得懂。
---------- 改后:枚举 + 简单工厂 ----------
订单:订单[A2001] 类型=普通单(1) 金额=100.00
[库存] 普通单 A2001:立即扣减库存
订单:订单[A2002] 类型=预售单(2) 金额=200.00
[库存] 预售单 A2002:只锁库存,不扣减
订单:订单[A2003] 类型=团购单(3) 金额=300.00
[库存] 团购单 A2003:按团购人数批量扣减
---------- 未知类型 code=9 怎么办 ----------
未知类型被拦下:未知订单类型 code=9:请先在 OrderType 里登记,再写对应的处理器
对比第 1 课:以前是打印一句话继续跑,订单直接丢失没人知道。
---------- 工厂方法:一门一个专人 ----------
[工厂方法] 使用处理器:NormalOrderHandler
[库存] 普通单 B3001:立即扣减库存
[工厂方法] 使用处理器:PresaleOrderHandler
[库存] 预售单 B3002:只锁库存,不扣减
[工厂方法] 使用处理器:GroupOrderHandler
[库存] 团购单 B3003:按团购人数批量扣减
---------- 抽象工厂:产品族不串味 ----------
[国内校验] C4001 金额399.0 元,校验通过
[国内通知] 给用户发短信:您的订单 C4001 已受理
[海外校验] C4002 金额888.0 美元,符合海外限额
[海外通知] 发英文邮件:Your order C4002 is confirmed
海外大额单被拦下:[海外校验] 单笔金额超过 10000 限额
========== 结束:新增订单类型 = 加一个枚举值 + 加一个处理器类,老代码一行不改 ==========
输出解读:
- A2001 立即扣、A2002 只锁、A2003 批量扣------分发结果和第 1 课一致,但业务代码里没有一个 if。
- code=9 被 catch 打印出来,对比第 1 课的"打印一句话溜掉",这是本课最大的改进:未知类型必须炸,不能静默。
- 工厂方法段先打印"使用处理器:NormalOrderHandler"再干活------这是
process()里固定写的,证明产品确实是各具体工厂造的。 - 抽象工厂段:888 美元在限额内通过,15000 超限额被抛------两族规则互不串味。
没看到输出?报 UnsupportedClassVersionError 说明 PATH 上的 java 是 JDK8,换上面那个全路径;中文乱码就先跑 [Console]::OutputEncoding=936。
五、生产环境落点:Spring 容器就是最大的工厂
先泼一盆冷水:真实业务项目里,别手写一堆静态工厂类当工厂用 。Spring 容器本身就是最大的工厂------@Component 注册 Bean、@Autowired 注入,你几乎不用自己 new。手写工厂只出现在两个地方:讲原理(就是本文),或者极轻量的工具对象创建。
但面试偏偏爱考 Spring 里这两个名字很像的接口,很多人挂在这:
BeanFactory:Spring 容器的顶层接口,它就是那个"超级大工厂",核心方法getBean(name)、getBean(type)。你@Autowired注入的每个 Bean,背后都是它在替你getBean(),管的是 Bean 怎么存、怎么取、什么作用域。FactoryBean:它自己也是一个 Bean,但它是"能生产别的 Bean 的 Bean",核心方法getObject()返回真正的产品。坑在这:你getBean("orderFactoryBean")拿到的不是 FactoryBean 本身,而是它getObject()造出来的产品 ;想拿 FactoryBean 自己,名字前面加&:getBean("&orderFactoryBean")。MyBatis 整合 Spring 时,MapperFactoryBean就是这么造出 Mapper 代理的。
JDK 里也到处是工厂:Calendar.getInstance() 是简单工厂(按时区返回不同日历),NumberFormat.getCurrencyInstance() 带点抽象工厂味道(按地区返回配套的数字/货币格式)。以后读源码看到 getInstance()、newInstance() 命名,先猜它是个工厂。
六、挑战题(改参数会怎样?答案在源码里)
- ⭐ 把
Main里int[] codes = {1, 2, 3};改成{1, 2, 9};再跑------控制台在哪一行停下来?A2003 还会打印吗?后面"工厂方法"那段还跑不跑?想清楚一个问题:同样是"未知类型炸掉",3.3 节那段 code=9 炸了没事,这里为什么把整个程序干停了? - ⭐⭐ 把
OverseasOrderComponentFactory里的限额10000改成20000,C4003(15000 那单)再跑------"海外大额单被拦下"这行还打印吗?结尾两行输出变成什么? - ⭐⭐ 给
OrderType加一个GROUPON(4, "拼团单")枚举值,Main里其他代码一行不动,再跑------程序在哪一行报错?抛的是哪个异常?顺着报错信息找到那个类,看看是哪段代码"救"了这次漏改。
三题答案都在 lesson-03 源码里,clone 下来改一行参数就能验证。跑出来和你想的不一样?评论区贴出来一起查。
七、面试回答模板
Q1:简单工厂、工厂方法、抽象工厂三兄弟有什么区别?
一句话结论:简单工厂一个方法造所有产品(不算 GoF),工厂方法一个工厂造一个产品,抽象工厂一个工厂造一族配套产品。展开:加新产品时,简单工厂要改 switch,工厂方法只加一个工厂类,抽象工厂要改接口和所有实现;选型口诀------整套切换用抽象工厂,产品种类自由扩展用工厂方法,两三种基本不变的小对象用简单工厂。(对应本文 2.2~2.4 节)
Q2:简单工厂为什么不算 GoF 23 种设计模式?
一句话结论:它没有"创建者抽象"这一层,是工厂方法的简化版,属于编程习惯。展开:GoF 的工厂方法要求产品抽象一层、创建者再抽象一层;简单工厂把创建逻辑塞进一个静态方法,省掉了创建者抽象,所以是阉割版。(对应本文 3.3 节)
Q3:怎么把一段"按类型 if-else 分发对象"的代码重构成工厂?
一句话结论:枚举消灭魔法数字 → 接口抽象产品 → 工厂集中创建 → 业务面向接口 。展开:先给类型建枚举配
fromCode(查不到抛异常,消灭静默失败),再把每种分支的行为抽成接口实现,最后把new收进工厂,业务代码只写"要对象",不写"造谁"。(对应本文 3.1~3.3 节)
Q4:BeanFactory 和 FactoryBean 有什么区别?
一句话结论:BeanFactory 是容器本身(大工厂),FactoryBean 是容器里一个特殊的 Bean,它的
getObject()负责再产出别的 Bean;getBean 拿它本身要加&前缀 。展开:@Autowired注入的每个 Bean 背后都是容器在getBean;而 FactoryBean 这种"工厂 Bean",你getBean("orderFactoryBean")拿到的是它生产的产品,getBean("&orderFactoryBean")才是它自己------MyBatis 的MapperFactoryBean就是这么造 Mapper 代理的。(对应本文第五节)
八、总结表
| 坑 | 原因 | 解法 |
|---|---|---|
type == 1/2/3 魔法数字 |
值写死,含义靠猜,改一处全项目遭殃 | OrderType 枚举登记合法类型,fromCode 反查抛异常(3.1 节) |
业务代码里到处 new 具体类 |
与实现焊死,换实现要回来改业务代码 | 工厂集中创建,业务只跟接口打交道(3.3 节) |
| 加一种产品就要改老 switch | 违反开闭原则 | 工厂方法:每种产品配一个具体工厂,老类不动(3.4 节) |
| 国内/海外规则容易配错 | 校验器和通知器是两套独立规则 | 抽象工厂:一族配套产品整套切换,不串味(3.5 节) |
| 未知类型打印一句话就溜 | 静默失败,线上丢单没人知道 | fromCode + default 双保险,不认识立刻炸(3.1、3.3 节) |
getBean 拿到的不是想要的 Bean |
FactoryBean 的产品和本体同名,缺 & 前缀 |
记住约定:& 前缀才拿 FactoryBean 自己(第五节) |
九、关于这个系列
本文是「Java 后端实战精通营」设计模式系列第 3 篇。这个系列不按 23 种模式教科书式罗列,只打三张牌:面试高频模式 + Spring/MyBatis 源码真实落点 + 坏代码实战重构------系列第 1 篇那份外卖烂代码,就是一路开药方的"手术对象"。
-
完整可运行工程:https://gitee.com/j67mk2/design-patterns(本课源码在
lesson-03/,主类com.dp.lesson03.Main,17 个类全在里面) -
下一篇预告 :《建造者模式:七个参数挤在一个构造器里,位置错一位全乱怎么办?》。上一课解决了"new 谁",下一课解决"new 的时候参数怎么给":第 1 课那个
Order七个参数挤在一个构造器里,我们用手写建造者把它变成builder().orderId().amount().build()的链式调用,顺便讲清 JDK 自带的StringBuilder就是建造者;再用原型模式演示Cloneable浅拷贝为什么会"改一个、另一个跟着变",深拷贝怎么写。
互动提问:你项目里有没有一段"按类型 if-else 创建对象"的代码?照今天这三兄弟一量,它该归谁管?评论区聊聊,我帮你选型。
本文部分内容由 AI 辅助生成