工厂模式:简单工厂、工厂方法与抽象工厂
工厂模式完全指南:简单工厂、工厂方法与抽象工厂
在面向对象编程中,创建对象是最常见的操作之一。但如果直接在代码中通过 new 关键字实例化具体类,会导致代码紧耦合,降低可扩展性和可维护性。工厂模式正是为了解决这类问题而生。它提供了一种将对象的创建与使用分离的方式,让你能够用更灵活、更符合开闭原则的方法生成对象。
本教程将带你系统学习工厂模式家族中的三种核心变体:
- 简单工厂 (Simple Factory)
- 工厂方法 (Factory Method)
- 抽象工厂 (Abstract Factory)
通过代码示例、结构图和实际场景分析,你将掌握它们的使用方法、优缺点以及彼此之间的区别。
1. 为什么需要工厂模式?
假设你正在开发一个披萨订购系统,最初的代码可能是这样的:
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else {
pizza = new DefaultPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
问题很明显:当需要新增一种披萨或修改披萨类型时,必须打开 PizzaStore 类并修改 orderPizza 方法。这违反了开闭原则(对扩展开放,对修改关闭)。同时,PizzaStore 与具体披萨类高度耦合,难以复用和测试。
工厂模式的核心思想:将对象创建的逻辑封装到一个独立的“工厂”类或方法中,让客户端只需关心工厂提供的抽象接口,而不用理会具体类的实例化过程。
2. 简单工厂模式 (Simple Factory)
简单工厂并非 GoF (四人帮) 定义的 23 种经典设计模式之一,它更像是一种编程习惯,但却是学习工厂模式的最佳起点。
2.1 定义与结构
简单工厂使用一个工厂类根据传入的参数动态决定应该创建哪一个产品类的实例。它通常包含一个静态方法,因此也被称为“静态工厂”。
结构角色:
- 工厂类 (Factory): 负责实现创建所有产品实例的逻辑,通常提供一个静态方法。
- 抽象产品 (Product): 定义产品的接口。
- 具体产品 (ConcreteProduct): 实现抽象产品接口的具体类。
2.2 代码示例
以披萨店为例,使用简单工厂重构:
// 抽象产品
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
// 具体产品
public class CheesePizza implements Pizza {
public void prepare() { System.out.println("准备芝士披萨"); }
public void bake() { System.out.println("烘烤"); }
public void cut() { System.out.println("切块"); }
public void box() { System.out.println("打包"); }
}
public class PepperoniPizza implements Pizza {
public void prepare() { System.out.println("准备意大利辣肠披萨"); }
// ... 实现其他方法类似
}
// 简单工厂
public class SimplePizzaFactory {
public static Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
// 未来可以扩展更多类型
return pizza;
}
}
// 客户端 - 披萨店
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = SimplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
现在,PizzaStore 不再直接依赖具体披萨类,而是通过工厂获取对象。当需要添加新品种时,只需在工厂方法中增加 else if 分支,客户端代码无需改动。
2.3 优缺点分析
优点:
- 实现简单,容易理解。
- 将对象的创建和使用分离,减轻客户端负担。
- 使用静态方法可直接调用,无需实例化工厂。
缺点:
- 工厂类集中了所有创建逻辑,一旦崩溃,整个系统会受到影响。
- 新增产品时仍需修改工厂类,违反开闭原则(对扩展开放,对修改关闭)。
- 随着产品种类增多,工厂方法会变得臃肿难以维护。
适用场景:
- 创建的对象类型较少,且客户端只知道传入工厂的参数。
- 工厂类负责创建的对象在业务逻辑中相对固定,不频繁扩展。
3. 工厂方法模式 (Factory Method)
工厂方法模式通过定义创建对象的接口,让子类决定实例化哪一个类,完美解决了简单工厂违反开闭原则的问题。它也是 GoF 23 种设计模式之一。
3.1 定义与结构
定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
结构角色:
- 抽象产品 (Product): 产品的接口。
- 具体产品 (ConcreteProduct): 实现产品接口的具体类。
- 抽象创建者 (Creator): 声明工厂方法,该方法返回一个产品对象。它也可能包含依赖于产品对象的其他业务方法(比如
orderPizza)。 - 具体创建者 (ConcreteCreator): 重写工厂方法以返回一个具体产品实例。
3.2 代码示例
让披萨店在不同地区加盟店 (纽约、芝加哥) 能够制作具有当地特色的披萨,同时保持制作流程 (orderPizza) 不变。
// 抽象产品
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
// 具体产品
public class NYStyleCheesePizza implements Pizza {
public void prepare() { System.out.println("准备纽约风格芝士披萨"); }
// ...
}
public class ChicagoStyleCheesePizza implements Pizza {
public void prepare() { System.out.println("准备芝加哥风格深盘芝士披萨"); }
// ...
}
// 抽象创建者
public abstract class PizzaStore {
// 工厂方法 - 由子类实现
protected abstract Pizza createPizza(String type);
// 订单处理逻辑,不随地区改变
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// 具体创建者
public class NYPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (type.equals("pepperoni")) {
return new NYStylePepperoniPizza();
}
return null;
}
}
public class ChicagoPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (type.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
}
return null;
}
}
// 客户端使用
public class PizzaTest {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
Pizza nyPizza = nyStore.orderPizza("cheese");
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza chicagoPizza = chicagoStore.orderPizza("cheese");
}
}
关键点: orderPizza 方法定义在抽象类中,它不关心具体创建哪种披萨,只调用抽象工厂方法 createPizza。子类只需实现这个工厂方法,就能在保持流程一致的同时创建出特定风格的产品。
3.3 优缺点分析
优点:
- 符合开闭原则:新增产品时只需扩展新的创建者子类,而无需修改已有代码。
- 符合单一职责原则:产品创建代码集中在独立的子类中。
- 客户端代码只依赖抽象,不依赖具体类,更易于扩展。
缺点:
- 每增加一个产品,就需要增加一个具体产品类和其对应的具体创建者类,导致类数量成倍增加,增加系统复杂度。
- 抽象层的引入增加了理解难度。
适用场景:
- 当一个类不知道它所必须创建的对象的类时。
- 当一个类希望由其子类来指定所创建的对象时。
- 当类的职责在于委托多个帮助子类中的一个作为创建者的责任,且你希望将哪个帮助子类是代理者这一信息局部化时。
4. 抽象工厂模式 (Abstract Factory)
抽象工厂模式用于创建一组相关或相互依赖的对象,而无需指定它们的具体类。它可以看作是工厂的工厂。
4.1 定义与结构
定义: 提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们具体的类。
结构角色:
- 抽象工厂 (AbstractFactory): 声明一组创建不同产品的方法,每个方法对应一个产品族中的产品。
- 具体工厂 (ConcreteFactory): 实现抽象工厂的创建方法,生成一个产品族的具体产品。
- 抽象产品 (AbstractProduct): 为一类产品声明接口。
- 具体产品 (ConcreteProduct): 实现抽象产品接口,由具体工厂创建。
- 客户端 (Client): 使用抽象工厂和抽象产品的接口。
4.2 代码示例
考虑到披萨店需要搭配不同的原料(面团、酱料、芝士等),且原料风格必须匹配(纽约店要用纽约原料,芝加哥店要用芝加哥原料)。抽象工厂负责创建整个原料家族。
// 抽象产品 - 原料
public interface Dough {
String toString();
}
public interface Sauce {
String toString();
}
public interface Cheese {
String toString();
}
// 具体产品 - 纽约原料
public class ThinCrustDough implements Dough {
public String toString() { return "薄饼面团"; }
}
public class MarinaraSauce implements Sauce {
public String toString() { return "意式番茄酱"; }
}
public class ReggianoCheese implements Cheese {
public String toString() { return "雷吉亚诺干酪"; }
}
// 具体产品 - 芝加哥原料
public class ThickCrustDough implements Dough {
public String toString() { return "厚饼面团"; }
}
public class PlumTomatoSauce implements Sauce {
public String toString() { return "李子番茄酱"; }
}
public class MozzarellaCheese implements Cheese {
public String toString() { return "马苏里拉奶酪"; }
}
// 抽象工厂 - 原料工厂
public interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
}
// 具体工厂 - 纽约原料工厂
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
}
// 具体工厂 - 芝加哥原料工厂
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThickCrustDough();
}
public Sauce createSauce() {
return new PlumTomatoSauce();
}
public Cheese createCheese() {
return new MozzarellaCheese();
}
}
// 抽象产品 - 披萨 (采用原料)
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Cheese cheese;
abstract void prepare(); // 准备过程使用原料工厂
void bake() { System.out.println("烘烤25分钟"); }
void cut() { System.out.println("切块"); }
void box() { System.out.println("打包"); }
// getters & setters 省略
}
// 具体产品
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
System.out.println("准备 " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
// 重构后的创建者
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("纽约风格芝士披萨");
}
// 其他类型...
return pizza;
}
}
现在,原料的创建完全由抽象工厂封装,客户端披萨店 (NYPizzaStore) 只需使用对应的原料工厂即可保证原料的一致性。
4.3 优缺点分析
优点:
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 符合开闭原则:增加新的产品族非常方便,无需修改已有工厂类。
- 隔离了具体类的生成,客户端只需通过抽象接口操作。
缺点:
- 产品族扩展困难:若需要新增一个产品(例如
Clam原料),必须修改抽象工厂以及所有具体工厂,改动巨大。 - 增加了系统的抽象性和理解难度。
适用场景:
- 系统中有多于一个的产品族,而每次只使用其中某一族。
- 系统要求产品对象必须保持一致风格时。
- 产品等级结构稳定,不会频繁新增产品类型,但可能扩展产品族。
5. 三种工厂模式对比
| 模式 | 核心思想 | 客户端依赖 | 扩展方式 | 关注点 |
|---|---|---|---|---|
| 简单工厂 | 一个工厂类根据参数返回不同产品对象 | 依赖工厂类 | 修改工厂类(违反开闭原则) | 单一产品对象创建 |
| 工厂方法 | 定义创建对象的接口,让子类决定实例化哪类 | 依赖抽象接口 | 增加新的具体创建者子类 | 单一产品等级结构的创建 |
| 抽象工厂 | 创建一系列相关或相互依赖的对象 | 依赖抽象工厂接口 | 增加新的具体工厂(产品族) | 产品族(多个产品)创建 |
演变关系:
简单工厂 -> 工厂方法:将创建逻辑从单一工厂下沉到子类工厂,解决简单工厂无法扩展的问题。
工厂方法 -> 抽象工厂:将创建对象从单一产品扩展到多个产品组成的家族,确保一族产品的一致性。
在实际开发中,这些模式经常组合使用,例如工厂方法中创建的具体产品可能又借助抽象工厂来生成所需原料。
6. 总结
- 简单工厂:入门首选,适用于对象类型少且变化不频繁的小型系统。记住它“只有一个工厂类”。
- 工厂方法:符合开闭原则,让子类决定创建什么对象。适合产品种类较多、需要灵活扩展的场景。核心是“继承”。
- 抽象工厂:主打产品族一致性,适用于需要创建多个系列、相互关联的对象的系统。核心是“对象组合”。
合理运用工厂模式,能显著降低耦合度,让你的代码更加易于维护和扩展。但也要注意,不要为了“用模式而用模式”,当系统规模较小时,直接创建对象简单明了,过度设计反而会引入不必要的复杂性。
在学习设计模式时,建议理解每种模式背后的意图和变化封装原则,这将帮助你在面对具体业务需求时做出最恰当的选择。