markdown复制## 1. 工厂方法模式引发的代码重构困局
上周团队里新来的小伙子在周会上抱怨:"我就想加个简单的导出PDF功能,结果发现要改二十几个文件!"这话让我想起五年前第一次接触工厂方法模式时踩过的坑。当时为了给电商系统增加微信支付,不得不把整个支付模块重写了80%。这种"牵一发而动全身"的架构问题,往往源于对工厂方法模式的误用或过度设计。
工厂方法模式(Factory Method Pattern)作为创建型设计模式的代表,本应是解耦的利器,但实际项目中经常看到两种极端:要么完全不用导致代码僵化,要么滥用造成过度抽象。真正掌握这个模式需要理解三个关键维度:
1. 创建逻辑与业务逻辑的分离程度
2. 产品族的扩展成本
3. 类型系统的约束边界
## 2. 工厂方法模式核心机制解析
### 2.1 模式的标准实现结构
典型的工厂方法模式包含四个角色:
- Product(抽象产品):定义产品的接口
- ConcreteProduct(具体产品):实现抽象产品的子类
- Creator(抽象工厂):声明工厂方法
- ConcreteCreator(具体工厂):实现工厂方法
```java
// 抽象产品
interface Document {
void save();
}
// 具体产品
class PdfDocument implements Document {
@Override
public void save() {
System.out.println("保存PDF文档");
}
}
// 抽象工厂
abstract class Application {
abstract Document createDocument();
void newDocument() {
Document doc = createDocument();
doc.save();
}
}
// 具体工厂
class PdfApplication extends Application {
@Override
Document createDocument() {
return new PdfDocument();
}
}
这种结构的优势在于新增文档类型时,只需扩展新的ConcreteProduct和ConcreteCreator,符合开闭原则。但实际工程中往往会出现三个典型问题:
2.2 现实项目中的常见变形
- 参数化工厂方法:通过传入类型参数决定创建对象
java复制class UniversalCreator {
Document createDocument(String type) {
switch(type) {
case "pdf": return new PdfDocument();
case "word": return new WordDocument();
default: throw new IllegalArgumentException();
}
}
}
这种写法虽然减少了工厂类数量,但违背了开闭原则——每次新增类型都需要修改方法体。
- 静态工厂方法:用静态方法替代工厂类
java复制class Documents {
static Document createPdf() {
return new PdfDocument();
}
}
牺牲了多态性的优势,难以实现工厂的子类化扩展。
- 混合创建逻辑:工厂方法中包含业务判断
java复制class ReportFactory {
Document createDocument(User user) {
if(user.isVip()) {
return new AdvancedPdfDocument();
} else {
return new BasicPdfDocument();
}
}
}
导致创建逻辑与业务规则耦合,增加测试复杂度。
3. 为什么加功能需要大改旧代码?
3.1 典型问题场景分析
假设我们有一个图形编辑器项目,初始只支持矩形和圆形:
java复制interface Shape {
void draw();
}
class Rectangle implements Shape {
public void draw() { /* 绘制矩形 */ }
}
class Circle implements Shape {
public void draw() { /* 绘制圆形 */ }
}
class ShapeFactory {
Shape createShape(String type) {
switch(type) {
case "rect": return new Rectangle();
case "circle": return new Circle();
default: throw new IllegalArgumentException();
}
}
}
当需要新增三角形支持时,会出现以下修改点:
- 创建新的Triangle类
- 修改ShapeFactory的createShape方法
- 更新所有调用createShape的地方的类型检查
- 修改相关单元测试
这种设计的问题在于:
- 违反单一职责原则(工厂类承担过多类型判断)
- 违反开闭原则(修改已有代码)
- 类型安全无法保障(字符串参数容易出错)
3.2 改进方案:经典工厂方法实现
java复制abstract class ShapeFactory {
abstract Shape createShape();
}
class RectangleFactory extends ShapeFactory {
@Override
Shape createShape() {
return new Rectangle();
}
}
class CircleFactory extends ShapeFactory {
@Override
Shape createShape() {
return new Circle();
}
}
// 新增三角形支持只需扩展
class TriangleFactory extends ShapeFactory {
@Override
Shape createShape() {
return new Triangle();
}
}
改进后:
- 新增类型只需添加新工厂类
- 编译期类型检查
- 工厂职责单一化
- 符合开闭原则
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
4. 工厂方法模式的进阶应用技巧
4.1 依赖注入与工厂模式的结合
现代框架通常结合DI容器实现更灵活的工厂:
java复制@Configuration
class AppConfig {
@Bean
@Scope("prototype")
Shape rectangle() {
return new Rectangle();
}
}
@Component
class ShapeService {
private final ObjectFactory<Shape> shapeFactory;
public ShapeService(ObjectFactory<Shape> factory) {
this.shapeFactory = factory;
}
void drawShape() {
Shape shape = shapeFactory.getObject();
shape.draw();
}
}
这种实现方式:
- 避免显式new操作
- 方便进行AOP增强
- 支持复杂的生命周期管理
4.2 多层级产品族的处理
当产品存在多个维度变化时,可以采用抽象工厂模式:
java复制interface GUIFactory {
Button createButton();
Menu createMenu();
}
class WinFactory implements GUIFactory {
public Button createButton() { return new WinButton(); }
public Menu createMenu() { return new WinMenu(); }
}
class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Menu createMenu() { return new MacMenu(); }
}
4.3 性能优化方案
对于频繁创建的对象,可以考虑:
- 对象池技术
- 享元模式
- 缓存机制
java复制class ShapePool {
private static final Map<Class<?>, Queue<Shape>> pool = new HashMap<>();
static Shape getShape(Class<? extends Shape> clazz) {
Queue<Shape> queue = pool.computeIfAbsent(clazz, k -> new LinkedList<>());
return queue.isEmpty() ? createNewShape(clazz) : queue.poll();
}
static void releaseShape(Shape shape) {
pool.get(shape.getClass()).offer(shape);
}
private static Shape createNewShape(Class<?> clazz) {
try {
return (Shape) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
5. 实战中的避坑指南
5.1 何时不该使用工厂方法
-
简单对象创建:当构造过程非常简单时,直接new更清晰
java复制// 反面示例 PointFactory.createPoint(x, y); // 更优方案 new Point(x, y); -
稳定不变的类型系统:如果产品类型几乎不会扩展
-
性能敏感场景:多一层抽象意味着额外的性能开销
5.2 常见设计陷阱
-
循环依赖:工厂与产品相互引用
java复制class ProductA { private final Factory factory; // 错误设计 } -
过度参数化:工厂方法参数过多
java复制// 难以维护的签名 Product createProduct(String type, int mode, boolean flag, Config config); -
违反Liskov替换原则:子类工厂返回不兼容类型
java复制class FraudFactory extends BaseFactory { // 错误:返回非Product子类 Object create() { return new Date(); } }
5.3 测试策略建议
- 为每个具体工厂编写单元测试
- 使用Mock验证对象创建过程
- 测试工厂方法的异常场景
- 基准测试创建性能
java复制@Test
void shouldCreatePdfDocument() {
Application app = new PdfApplication();
Document doc = app.createDocument();
assertTrue(doc instanceof PdfDocument);
}
@Test
void shouldThrowWhenInvalidType() {
UniversalFactory factory = new UniversalFactory();
assertThrows(IllegalArgumentException.class,
() -> factory.create("invalid"));
}
6. 现代化演进趋势
6.1 函数式工厂
Java 8之后的lambda简化了工厂实现:
java复制Supplier<Shape> circleFactory = Circle::new;
Shape circle = circleFactory.get();
6.2 基于注解的工厂
Spring风格的实现:
java复制@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ShapeType {
String value();
}
@ShapeType("circle")
class Circle implements Shape {}
class ShapeFactory {
private Map<String, Supplier<Shape>> registry = new HashMap<>();
void register(String type, Supplier<Shape> supplier) {
registry.put(type, supplier);
}
Shape create(String type) {
return registry.get(type).get();
}
}
6.3 模式组合实践
结合建造者模式实现复杂对象创建:
java复制class ReportBuilderFactory {
ReportBuilder createBuilder(ReportType type) {
switch(type) {
case FINANCIAL:
return new FinancialReportBuilder()
.withHeader(true)
.withFooter(true);
case SIMPLE:
return new SimpleReportBuilder();
default:
throw new IllegalArgumentException();
}
}
}
在实际工程中,我逐渐形成了三条经验法则:
- 当发现自己在复制粘贴对象创建代码时,考虑引入工厂方法
- 当switch-case类型判断超过3个分支时,重构为多态工厂
- 当单元测试需要频繁mock对象创建时,抽象出工厂接口
这些年来,工厂方法模式就像是一把瑞士军刀——用对了场景能优雅解决问题,但强行套用反而会增加复杂度。关键在于理解其本质:将"用什么"和"怎么创建"这两个关注点分离。
code复制
