一、什么是抽象工廠模式
抽象工廠模式其實就是多個工廠方法模式,比如前面工廠方法模式中,我們創建多個不同類型的數據庫,有MySQL、SQLServer等等,就是用工廠方法模式來實現的,但此時我們只能實現一個表(具體內容見下方工廠模式的實現),我們數據庫中當然不可能只有一個表呀,所以抽象工廠模式就來了。
抽象工廠模式(Abstract Factory),提供一個創建一系列相關或相互依賴對象的接口,而無需指定它們具體的類。UML結構圖如下:

其中,AbstractFactory是抽象工廠接口,里面包含所有的產品創建的抽象方法;ConcreteFactory則是具體的工廠,創建具有特定實現的產品對象;AbstractProduct是抽象產品,有可能由兩種不同的實現;ConcreteProduct則是對於抽象產品的具體分類的實現。
抽象工廠模式是工廠方法模式的升級版本,在有多個業務品種、業務分類時,通過抽象工廠模式產生需要的對象是一種非常好的解決方式。下面是抽象工廠模式的通用源代碼類圖:

1. AbstractFactory類
下述代碼是一個抽象工廠類,它的職責是定義每個工廠要實現的功能,有n個產品族,在抽象工廠類中就應該有n個創建方法。這里按上述類圖,給出A、B兩個產品族,即構造兩個方法。
1 public abstract class AbstractFactory { 2 3 //創建A產品家族 4 public abstract AbstractProductA createProductA(); 5 //創建B產品家族 6 public abstract AbstractProductB createProductB(); 7 8 }
2. AbstractProduct類
抽象產品類,兩個抽象產品類可以有關系,例如共同繼承或實現一個抽象類或接口。這里給出A產品的抽象類,產品類B類似,不再贅述。
1 public abstract class AbstractProductA { 2 3 //每個產品共有的方法 4 public void shareMethod() {} 5 //每個產品相同方法,不同實現 6 public abstract void doSomething(); 7 8 }
3. ConcreteFactory類
具體工廠實現類,如何創建一個產品是由具體的實現類來完成的。下方給出產品等級1的實現類,等級2同理。
1 public class ConcreteFactory1 extends AbstractFactory { 2 3 @Override 4 public AbstractProductA createProductA() { 5 return new ProductA1(); 6 } 7 8 @Override 9 public AbstractProductB createProductB() { 10 return new ProductB1(); 11 } 12 13 }
4. ConcreteProduct類
兩個具體產品的實現類,這里只給出A的兩個具體產品類,B與此類似。
1 public class ProductA1 extends AbstractProductA { 2 3 @Override 4 public void doSomething() { 5 System.out.println("產品A1實現方法"); 6 } 7 8 }
1 public class ProductA2 extends AbstractProductA { 2 3 @Override 4 public void doSomething() { 5 System.out.println("產品A2實現方法"); 6 } 7 8 }
5. Client客戶端
1 public class Client { 2 3 public static void main(String[] args) { 4 //定義兩個工廠 5 AbstractFactory factory1 = new ConcreteFactory1(); 6 AbstractFactory factory2 = new ConcreteFactory2(); 7 8 //產生A1對象 9 AbstractProductA a1 = new ProductA1(); 10 //產生A2對象 11 AbstractProductA a2 = new ProductA2(); 12 //產生B1對象 13 AbstractProductB b1 = new ProductB1(); 14 //產生B2對象 15 AbstractProductB b2 = new ProductB2(); 16 17 //.... 18 } 19 20 }
二、抽象工廠模式的應用
1. 何時使用
- 系統的產品有多於一個的產品族,而系統只消費其中某一族的產品時。
2. 優點
- 封裝性,易於產品交換。由於具體工廠類在一個應用中只需在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,只需改變具體工程即可使用不同的產品配置。
- 創建實例過程與客戶端分離。
3. 缺點
- 產品族擴展非常困難,改動或增加一個產品需同時改動多個類。
4. 使用場景
- 一個對象族(或一組沒有任何關系的對象)都有相同的約束。
5. 應用實例
- 生成不同操作系統的程序。
- QQ換皮膚,一整套一起換。
- 更換數據庫。
三、工廠方法模式的實現
在看抽象工廠模式之前,我們先用工廠方法模式試一下。
以模擬更換數據庫為例,UML圖如下:

這里我們先只對User表進行操作,所以工廠方法只有CreateUser()。
1. IFactory接口
定義一個創建訪問User表對象的抽象的工廠接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 5 }
2. IUser接口
用於客戶端訪問,解除與具體數據庫訪問的耦合。模擬插入方法insert。
1 public interface IUser { 2 3 public void insert(User user); 4 public User getUser(int id); 5 6 }
3. SqlserverUser類
用於訪問SQLServer的User。
1 public class SqlserverUser implements IUser { 2 3 @Override 4 public void insert(User user) { 5 System.out.println("insert info into user with sqlserver"); 6 } 7 8 @Override 9 public User getUser(int id) { 10 System.out.println("get info from user by id with sqlserver"); 11 return null; 12 } 13 14 }
4. AccessUser類
用於訪問Access的User。
1 public class AccessUser implements IUser { 2 3 @Override 4 public void insert(User user) { 5 System.out.println("insert info into user with access"); 6 } 7 8 @Override 9 public User getUser(int id) { 10 System.out.println("get info from user by id with access"); 11 return null; 12 } 13 14 }
5. SqlserverFactory類
實現IFactory接口,實例化SqlserverFactory。
1 public class SqlserverFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new SqlserverUser(); 6 } 7 8 }
6. AccessFactory類
實現IFactory接口,實例化AccessFactory。
1 public class AccessFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new AccessUser(); 6 } 7 8 }
7. Client客戶端
1 public class Client { 2 3 public static void main(String[] args) { 4 User user = new User(); 5 6 IFactory factory = new SqlserverFactory(); 7 // IFactory factory = new AccessFactory(); 8 9 IUser iUser = factory.createUser(); 10 iUser.insert(user); 11 iUser.getUser(1); 12 } 13 14 }
這就是工廠方法模式的實現,現在只需把new SqlserverFactory()改成下方注釋那樣的new AccessFactory()就可以實現更換數據庫了,此時由於多態的關系,使得聲明IUser接口的對象iUser事先根本不知道是在訪問哪個數據庫,卻可以在運行時很好地完成工作,這就是所謂的業務邏輯與數據訪問的解耦。
具體工廠方法模式可參考之前的文章:簡說設計模式——工廠方法模式。
四、抽象工廠模式的實現
上面用工廠方法模式實現了模擬更換數據庫,但數據庫中不可能只存在一個表,如果有多個表的情況又該如何呢?我們試着再增加一個表,比如增加一個部門表(Department表)。UML圖如下:

1. IFactory接口
先更改一下IFactory接口,增加一個創建訪問Department表對象的抽象的工廠接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 IDepartment createDepartment(); 5 6 }
2. IDepartment接口
增加一個IDepartment接口,用於客戶端訪問。
1 public interface IDepartment { 2 3 public void insert(Department department); 4 public Department getDepartment(int id); 5 6 }
3. 數據庫工廠
在sqlserver數據庫工廠中實例化SqlserverDepartment,Access同理。
1 public class SqlserverFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new SqlserverUser(); 6 } 7 8 @Override 9 public IDepartment createDepartment() { 10 return new SqlserverDepartment(); 11 } 12 13 }
4. IDepartment接口的實現類
增加SqlserverDepartment及AccessDepartment。
1 public class SqlserverDepartment implements IDepartment { 2 3 @Override 4 public void insert(Department department) { 5 System.out.println("insert info into department with sqlserver"); 6 } 7 8 @Override 9 public Department getDepartment(int id) { 10 System.out.println("get info from department by id with sqlserver"); 11 return null; 12 } 13 14 }
5. Client客戶端
在客戶端中增加department的實現。
1 public class Client { 2 3 public static void main(String[] args) { 4 User user = new User(); 5 Department department = new Department(); 6 7 IFactory factory = new SqlserverFactory(); 8 // IFactory factory = new AccessFactory(); 9 10 IUser iUser = factory.createUser(); 11 iUser.insert(user); 12 iUser.getUser(1); 13 14 IDepartment iDepartment = factory.createDepartment(); 15 iDepartment.insert(department); 16 iDepartment.getDepartment(1); 17 } 18 19 }
如上述代碼,運行sqlserver數據庫的結果如下:

若更換為access數據庫,運行結果如下:

剛才我們只有一個User類和User操作類的時候,只需要工廠方法模式即可,但現在顯然數據庫中有許多的表,而SQLServer和Access又是兩個不同的分類,解決這種涉及到多個產品系列的問題,就用到了抽象工廠模式。
五、利用反射實現數據訪問程序
在上述的兩種模式中,我們是有多少個數據庫就要創建多少個數據庫工廠,而我們數據庫工廠中的代碼基本上是相同的,這時就可以使用簡單工廠模式+抽象工廠模式來簡化操作,也即將工廠類及工廠接口拋棄,取而代之的是DataAccess類,由於實現設置了db的值(Sqlserver或Access),所以簡單工廠的方法都不需要輸入參數,這樣客戶端可以直接生成具體的數據庫訪問類實例,且沒有出現任何一個SQLServer或Access的字樣,達到解耦的目的。可以看一下UML圖:

但此時還有一個問題就是,因為DataAccess中創建實例的過程使用的是switch語句,所以如果此時要增加一個數據庫,比如Oracle數據庫,就需要修改每個switch的case了,違背了開閉原則。對於這種情況,工廠方法模式里有提到過,就是使用反射機制,或者這里應該更確切的說是依賴注入(DI),spring的IoC中有遇到這個概念。
這里我們可以直接使用反射來利用字符串去實例化對象,這樣變量就是可更換的了,換句話說就是將程序由編譯時轉為運行時,如下:
1 public class DataAccess { 2 3 private static final String name = "com.adamjwh.gofex.abstract_factory"; 4 private static final String db = "Access"; 5 6 public static IUser createUser() throws InstantiationException, IllegalAccessException, ClassNotFoundException { 7 String className = name + "." + db + "User"; 8 return (IUser) Class.forName(className).newInstance(); 9 } 10 11 public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException { 12 String className = name + "." + db + "Department"; 13 return (IDepartment) Class.forName(className).newInstance(); 14 } 15 16 }
這里字符串name為包名,db為要查詢的數據庫名,我們要更換數據庫只需將Access修改成我們需要的數據庫名即可,這時只需修改DataAccess類即可,然后我們再在Client中對DataAccess類實例化。
DataAccess factory = new DataAccess();
當然我們還可以利用配置文件來解決更改DataAccess的問題,此時就連DataAccess類都不用更改了,如下:
1 <?xml version="1.0 encoding="utf-8" ?> 2 <configuration> 3 <appSettings> 4 <add key="DB" value="Oracle" /> 5 </appSettings> 6 </configuration>
所以,所有在用簡單工廠的地方,都可以考慮用反射技術來去除swithc或if,解除分支判斷帶來的耦合。
