抽象工厂是一种创建型设计模式,可以生成相关对象系列,而无需指定它们的具体类。
假设你正在写一个家具店模拟器。
你的代码这些类组成:
你需要一种方法来创建家具对象,确保它们与同一风格的其他对象相匹配。如果客户收到风格不匹配的家具,他们就会非常生气。
另外,在向程序添加新产品或产品系列时,你也不想更改现有代码。
按照惯例,大家一开始会怎么实现?
// 直接实例化具体家具类导致风格混杂
class Client {
public void createLivingRoom() {
// 混合使用不同风格组件(致命错误)
Chair modernChair = new ModernChair();
Sofa victorianSofa = new VictorianSofa();
CoffeeTable artDecoTable = new ArtDecoCoffeeTable();
modernChair.sit();
victorianSofa.lieDown();
artDecoTable.placeMagazine();
}
}
// 具体产品实现
class ModernChair extends Chair {
public void sit() { System.out.println("Modern chair sitting"); }
}
class VictorianSofa extends Sofa {
public void lieDown() { System.out.println("Victorian sofa relaxing"); }
}
class ArtDecoCoffeeTable extends CoffeeTable {
public void placeMagazine() { System.out.println("ArtDeco table placement"); }
}
客户端直接创建不同风格的对象(现代椅子+维多利亚沙发)
╭── 问题场景 ──╮
用户订单要求"维多利亚风格客厅"时:
┌─────────────────┬──────────────────────┐
│ 预期组合 │ 实际可能创建的组合 │
├─────────────────┼──────────────────────┤
│ VictorianChair │ VictorianChair │
│ VictorianSofa │ ModernSofa ←不匹配 │
│ VictorianTable │ ArtDecoTable ←灾难性 │
└─────────────────┴──────────────────────┘
结果:客户收到风格冲突的家具套装
每当新增风格时(如新增ArtDeco),强制修改所有客户端代码
// 新增风格场景产生连锁修改
class Client {
// 必须添加新分支判断
public void createSet(String style) {
if ("ArtDeco".equals(style)) { // 破坏开放封闭原则
chair = new ArtDecoChair(); // 需要新增多个类引用
sofa = new ArtDecoSofa();
}
}
}
缺乏统一约束机制,易出现类型错误
// 错误将现代餐桌与维多利亚椅组合(类型系统无法阻止)
FurnitureSet set = new FurnitureSet(
new ModernChair(),
new VictorianDiningTable() // 应该抛出异常但现有代码无法约束
);
抽象工厂模式建议的第一件事就是明确声明产品系列中每个不同产品的接口(例如,椅子、沙发或咖啡桌)。然后,让所有风格的产品都实现这些接口。例如,所有风格的椅子都可以实现 Chair 接口;所有风格的咖啡桌都可以实现 CoffeeTable 接口,等等。
// 接口约束产品规格
public interface Chair {
void sit();
}
public interface Sofa {
void lieDown();
}
public interface CoffeeTable {
void placeItem();
}
// 确保现代系列组件统一
public class ModernChair implements Chair {
@Override
public void sit() {
System.out.println("Modern chair designed seating");
}
}
public class ModernSofa implements Sofa {
@Override
public void lieDown() {
System.out.println("Modern sofa clean lines design");
}
}
public class ModernCoffeeTable implements CoffeeTable {
@Override
public void placeItem() {
System.out.println("Modern geometric table surfaces");
}
}
// 保证维多利亚风格一致性
public class VictorianChair implements Chair {
@Override
public void sit() {
System.out.println("Classic carved wood chair");
}
}
public class VictorianSofa implements Sofa {
@Override
public void lieDown() {
System.out.println("Antique fabric sofa");
}
}
public class VictorianCoffeeTable implements CoffeeTable {
@Override
public void placeItem() {
System.out.println("Ornate marble-top table");
}
}
下一步是声明抽象工厂(接口),其中包含特定产品系列所有产品的创建方法列表(例如,createChair、createSofa 和 createCoffeeTable)。这些方法必须返回我们之前定义的抽象产品类型接口:Chair、Sofa、CoffeeTable 等等。对于产品的每种风格,我们基于 AbstractFactory 接口创建一个单独的工厂类。这个工厂类是返回特定类型产品的类。例如,ModernFurnitureFactory 只能创建 ModernChair、ModernSofa 和 ModernCoffeeTable 对象。
public interface FurnitureFactory {
Chair createChair();
Sofa createSofa();
CoffeeTable createCoffeeTable();
}
// 现代风格产品线工厂
public class ModernFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new ModernChair();
}
@Override
public Sofa createSofa() {
return new ModernSofa();
}
@Override
public CoffeeTable createCoffeeTable() {
return new ModernCoffeeTable();
}
}
// 维多利亚风格产品线工厂
public class VictorianFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new VictorianChair();
}
@Override
public Sofa createSofa() {
return new VictorianSofa();
}
@Override
public CoffeeTable createCoffeeTable() {
return new VictorianCoffeeTable();
}
}
客户端代码必须通过抽象接口来和工厂、产品协作。这样就可以更改传给客户端代码的工厂的类型以及客户端代码接收的产品风格,而不破坏实际的客户端代码。
假设客户希望工厂生产一把椅子。客户不必知道工厂的类别,也不必关心它得到的椅子是什么类型。无论是现代风格还是维多利亚风格的椅子,客户都必须使用抽象Chair接口以相同的方式处理所有椅子。唯一知道的是sitOn方法。此外,无论返回哪种椅子,它总是与同一工厂对象生产的沙发或咖啡桌风格相匹配。
public class FurnitureStore {
private FurnitureFactory factory;
// 动态绑定具体工厂
public FurnitureStore(FurnitureFactory factory) {
this.factory = factory;
}
public void showcaseSet() {
Chair chair = factory.createChair();
Sofa sofa = factory.createSofa();
CoffeeTable table = factory.createCoffeeTable();
System.out.println("展示完整风格套件:");
chair.sit();
sofa.lieDown();
table.placeItem();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 创建现代风格商店
FurnitureStore modernStore = new FurnitureStore(new ModernFactory());
modernStore.showcaseSet();
// 创建维多利亚风格商店
FurnitureStore victorianStore = new FurnitureStore(new VictorianFactory());
victorianStore.showcaseSet();
}
}
还有一件事需要明确:如果客户端只接触抽象接口,那是什么创建了实际的工厂对象(即new ModernFactory())?通常,应用程序在初始化阶段创建一个具体的工厂对象。在这之前,应用程序必须根据配置或环境设置选择工厂类型。