文章目錄
- 簡介
- 場景
- 問題
- 1. 風格一致性失控
- 2. 對象創建硬編碼
- 3. 產品族管理失效
- 解決
- 總結
簡介
抽象工廠是一種創建型設計模式,可以生成相關對象系列,而無需指定它們的具體類。
場景
假設你正在寫一個家具店模擬器。
你的代碼這些類組成:
- 相關產品系列,例如:椅子 + 沙發 + 咖啡桌。
- 此系列有多種風格。例如,椅子 + 沙發 + 咖啡桌系列有以下風格:現代、維多利亞、裝飾藝術。
你需要一種方法來創建家具對象,確保它們與同一風格的其他對象相匹配。如果客戶收到風格不匹配的家具,他們就會非常生氣。
另外,在向程序添加新產品或產品系列時,你也不想更改現有代碼。
按照慣例,大家一開始會怎么實現?
// 直接實例化具體家具類導致風格混雜
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"); }
}
問題
1. 風格一致性失控
客戶端直接創建不同風格的對象(現代椅子+維多利亞沙發)
╭── 問題場景 ──╮
用戶訂單要求"維多利亞風格客廳"時:
┌─────────────────┬──────────────────────┐
│ 預期組合 │ 實際可能創建的組合 │
├─────────────────┼──────────────────────┤
│ VictorianChair │ VictorianChair │
│ VictorianSofa │ ModernSofa ←不匹配 │
│ VictorianTable │ ArtDecoTable ←災難性 │
└─────────────────┴──────────────────────┘
結果:客戶收到風格沖突的家具套裝
2. 對象創建硬編碼
每當新增風格時(如新增ArtDeco),強制修改所有客戶端代碼
// 新增風格場景產生連鎖修改
class Client {// 必須添加新分支判斷public void createSet(String style) {if ("ArtDeco".equals(style)) { // 破壞開放封閉原則chair = new ArtDecoChair(); // 需要新增多個類引用sofa = new ArtDecoSofa();}}
}
3. 產品族管理失效
缺乏統一約束機制,易出現類型錯誤
// 錯誤將現代餐桌與維多利亞椅組合(類型系統無法阻止)
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 {@Overridepublic void sit() { System.out.println("Modern chair designed seating"); }
}public class ModernSofa implements Sofa {@Overridepublic void lieDown() { System.out.println("Modern sofa clean lines design"); }
}public class ModernCoffeeTable implements CoffeeTable {@Overridepublic void placeItem() { System.out.println("Modern geometric table surfaces"); }
}// 保證維多利亞風格一致性
public class VictorianChair implements Chair {@Overridepublic void sit() { System.out.println("Classic carved wood chair"); }
}public class VictorianSofa implements Sofa {@Overridepublic void lieDown() { System.out.println("Antique fabric sofa"); }
}public class VictorianCoffeeTable implements CoffeeTable {@Overridepublic 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 {@Overridepublic Chair createChair() { return new ModernChair(); }@Overridepublic Sofa createSofa() { return new ModernSofa(); }@Overridepublic CoffeeTable createCoffeeTable() { return new ModernCoffeeTable(); }
}// 維多利亞風格產品線工廠
public class VictorianFactory implements FurnitureFactory {@Overridepublic Chair createChair() { return new VictorianChair(); }@Overridepublic Sofa createSofa() { return new VictorianSofa(); }@Overridepublic 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())?通常,應用程序在初始化階段創建一個具體的工廠對象。在這之前,應用程序必須根據配置或環境設置選擇工廠類型。
總結
- 抽象產品(Abstract Prod-uct):構成所有產品的一組接口。
- 具體產品(Con-crete Prod-uct):抽象產品的多種不同類型實現。所有風格產品(維多利亞/現代)都必須實現相應的抽象產品(椅子/沙發)。
- 抽象工廠(Abstract Fac-to-ry)接口:聲明了一組創建各種抽象產品的方法。
- 具體工廠(Con-crete Fac-to-ry):實現抽象工廠的產品創建方法。每個具體工廠都對應特定風格的產品,且只能創建這一種風格的產品。
- 盡管具體工廠會對具體產品進行初始化,它的創建方法必須返回相應的抽象產品。只有這樣,使用工廠類的客戶端代碼就不會與工廠創建的特定風格產品耦合。客戶端(Client)只需通過抽象接口調用工廠和產品對象,就可以跟任何具體工廠/產品進行交互。