## 引入
每一個模式都是針對一定問題的解決方案。抽象工廠模式與工廠方法模式的最大區別就在于,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則需要面對多個產品等級結構。
在學習抽象工廠具體實例之前,應該明白兩個重要的概念:產品族和產品等級。
**產品等級結構**:產品的等級結構也就是產品的繼承結構。例如一個為空調的抽象類,它有海爾空調、格力空調、美的空調等一系列的子類,那么這個抽象類空調和他的子類就構成了一個產品等級結構。
**產品族**:產品族是在抽象工廠模式中的。在抽象工廠模式中,產品族是指由同一個工廠生產的,位于不同產品等級結構中的一組產品。比如,海爾工廠生產海爾空調。海爾冰箱,那么海爾空調則位于空調產品族中。
如下圖示例:

顯然,每一個產品族中含有產品的數目,與產品等級結構的數目是相等的。產品的等級結構與產品族將產品按照不同方向劃分,形成一個二維的坐標系。橫軸表示產品的等級結構,縱軸表示產品族,上圖共有兩個產品族,分布于三個不同的產品等級結構中。只要指明一個產品所處的產品族以及它所屬的等級結構,就可以唯一的確定這個產品。
上面所給出的三個不同的等級結構具有平行的結構。因此,如果采用工廠方法模式,就勢必要使用三個獨立的工廠等級結構來對付這三個產品等級結構。由于這三個產品等級結構的相似性,會導致三個平行的工廠等級結構。隨著產品等級結構的數目的增加,工廠方法模式所給出的工廠等級結構的數目也會隨之增加。

那么,是否可以使用同一個工廠等級結構來對付這些相同或者極為相似的產品等級結構呢?當然可以的,而且這就是抽象工廠模式的好處。同一個工廠等級結構負責三個不同產品等級結構中的產品對象的創建。

## 基本定義
抽象工廠模式提供一個接口,用于創建相關或者依賴對象的家族,而不需要明確指定具體類。
抽象工廠允許客戶端使用抽象的接口來創建一組相關的產品,而不需要關系實際產出的具體產品是什么。這樣一來,客戶就可以從具體的產品中被解耦。
## 模式結構
抽象工廠模式的UML結構圖如下:

* AbstractFactory:抽象工廠。抽象工廠定義了一個接口,所有的具體工廠都必須實現此接口,這個接口包含了一組方法用來生產產品。
* ConcreteFactory:具體工廠。具體工廠是用于生產不同產品族。要創建一個產品,客戶只需要使用其中一個工廠完全不需要實例化任何產品對象。
* AbstractProduct:抽象產品。這是一個產品家族,每一個具體工廠都能夠生產一整組產品。
* Product:具體產品。
## 代碼實現
為了要保證每家加盟店都能夠生產高質量的披薩,防止使用劣質的原料,我們打算建造一家生產原料的工廠,并將原料運送到各家加盟店。但是加盟店都位于不同的區域,比如紐約、芝加哥。紐約使用一組原料,芝加哥使用另一種原料。在這里我們可以這樣理解,這些不同的區域組成了原料家族,每個區域實現了一個完整的原料家族。
首先創建一個原料工廠。該工廠為抽象工廠,負責創建所有的原料。
PizzaIngredientFactory.java
public interface PizzaIngredientFactory {
~~~
/*
* 在接口中,每個原料都有一個對應的方法創建該原料
*/
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}
~~~
紐約原料工廠:NYPizzaIngredientFactory.java。
~~~
public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Clams createClams() {
return new FreshClams();
}
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {new Garlic(),new Onion(),new Mushroom(),new RefPepper()};
return veggies;
}
}
~~~
重新返回到披薩。在這個披薩類里面,我們需要使用原料,其他方法保持不變,將prepare()方法聲明為抽象,在這個方法中,我們需要收集披薩所需要的原料。
Pizza.java
~~~
public abstract class Pizza {
/*
* 每個披薩都持有一組在準備時會用到的原料
*/
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
/*
* prepare()方法聲明為抽象方法。在這個方法中,我們需要收集披薩所需要的原料,而這些原料都是來自原料工廠
*/
abstract void prepare();
void bake(){
System.out.println("Bake for 25 munites at 350");
}
void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
~~~
CheesePizza.java
~~~
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
/*
* 要制作披薩必須要有制作披薩的原料,而這些原料是從原料工廠運來的
*/
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
prepare();
}
/**
* 實現prepare方法
* prepare 方法一步一步地創建芝士比薩,每當需要原料時,就跟工廠要
*/
void prepare() {
System.out.println("Prepareing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
~~~
Pizza的代碼利用相關的工廠生產原料。所生產的原料依賴所使用的工廠,Pizza類根本不關心這些原料,它只需要知道如何制作披薩即可。這里,Pizza和區域原料之間被解耦。
ClamPizza.java
~~~
public class ClamPizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public ClamPizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
System.out.println("Prepare " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clams = ingredientFactory.createClams();
}
}
~~~
做完披薩后,需要關注披薩店了。
在披薩店中,我們依然需要關注原料,當地的披薩店需要和本地的原料工廠關聯起來。
PizzaStore.java
~~~
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
/*
* 創建pizza的方法交給子類去實現
*/
abstract Pizza createPizza(String type);
}
~~~
紐約的披薩店:NYPizzaStore.java
~~~
public class NYPizzaStore extends PizzaStore{
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
//使用紐約的原料工廠
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if("cheese".equals(type)){
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}
else if("veggie".equals(type)){
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
}
else if("clam".equals(type)){
pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");
}
else if("pepperoni".equals(type)){
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");
}
return pizza;
}
}
~~~
其中PizzaIngredientFactory是抽象的披薩原料工廠接口,它定義了如何生產一個相關產品的家族。這個家族包含了所有制作披薩的原料。
NYPizzaIngredientFactory和ChicagoPizzaIngredientFactory是兩個具體披薩工廠類,他們負責生產相應的披薩原料。
NYPizzaStore是抽象工廠的客戶端。
## 優點
* 1、分離接口和實現,抽象工廠隔離了具體類的生成,使得客戶端不需要知道什么被創建。所有的具體工廠都實現了抽象工廠中定義的公共接口,因此只需要改變具體工廠的實例,就可以在某種程度上改變整個軟件系統的行為。
* 2、 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象。
## 缺點
* 不太容易擴展新的產品 添加新的行為時比較麻煩。如果需要添加一個新產品族對象時,需要更改接口及其下所有子類,這必然會帶來很大的麻煩。
## 使用場景
* 1. 一個系統不應當依賴于產品類實例如何被創建、組合和表達的細節,這對于所有類型的工廠模式都是重要的。
* 2.系統中有多于一個的產品族,而每次只使用其中某一產品族。
* 3. 屬于同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。
* 4. 系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴于具體實現。
## 起源
抽象工廠模式的起源或者最早的應用,是用于創建分屬于不同操作系統的視窗構建。比如:命令按鍵(Button)與文字框(Text)都是視窗構建,在UNIX操作系統的視窗環境和Windows操作系統的視窗環境中,這兩個構建有不同的本地實現,它們的細節有所不同。
在每一個操作系統中,都有一個視窗構建組成的構建家族。在這里就是Button和Text組成的產品族。而每一個視窗構件都構成自己的等級結構,由一個抽象角色給出抽象的功能描述,而由具體子類給出不同操作系統下的具體實現。

可以發現在上面的產品類圖中,有兩個產品的等級結構,分別是Button等級結構和Text等級結構。同時有兩個產品族,也就是UNIX產品族和Windows產品族。UNIX產品族由UNIX Button和UNIX Text產品構成;而Windows產品族由Windows Button和Windows Text產品構成。

系統對產品對象的創建需求由一個工程的等級結構滿足,其中有兩個具體工程角色,即UnixFactory和WindowsFactory。UnixFactory對象負責創建Unix產品族中的產品,而WindowsFactory對象負責創建Windows產品族中的產品。這就是抽象工廠模式的應用,抽象工廠模式的解決方案如下圖:

顯然,一個系統只能夠在某一個操作系統的視窗環境下運行,而不能同時在不同的操作系統上運行。所以,系統實際上只能消費屬于同一個產品族的產品。
在現代的應用中,抽象工廠模式的使用范圍已經大大擴大了,不再要求系統只能消費某一個產品族了。因此,可以不必理會前面所提到的原始用意。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux