抽象工廠模式其實就是多個工廠方法模式,好比前面工廠方法模式中,咱們建立多個不一樣類型的數據庫,有MySQL、SQLServer等等,就是用工廠方法模式來實現的,但此時咱們只能實現一個表(具體內容見下方工廠模式的實現),咱們數據庫中固然不可能只有一個表呀,因此抽象工廠模式就來了。html
抽象工廠模式(Abstract Factory),提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。UML結構圖以下:git
其中,AbstractFactory是抽象工廠接口,裏面包含全部的產品建立的抽象方法;ConcreteFactory則是具體的工廠,建立具備特定實現的產品對象;AbstractProduct是抽象產品,有可能由兩種不一樣的實現;ConcreteProduct則是對於抽象產品的具體分類的實現。spring
抽象工廠模式是工廠方法模式的升級版本,在有多個業務品種、業務分類時,經過抽象工廠模式產生須要的對象是一種很是好的解決方式。下面是抽象工廠模式的通用源代碼類圖:sql
下述代碼是一個抽象工廠類,它的職責是定義每一個工廠要實現的功能,有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 }
抽象產品類,兩個抽象產品類能夠有關係,例如共同繼承或實現一個抽象類或接口。這裏給出A產品的抽象類,產品類B相似,再也不贅述。設計模式
1 public abstract class AbstractProductA { 2 3 //每一個產品共有的方法 4 public void shareMethod() {} 5 //每一個產品相同方法,不一樣實現 6 public abstract void doSomething(); 7 8 }
具體工廠實現類,如何建立一個產品是由具體的實現類來完成的。下方給出產品等級1的實現類,等級2同理。markdown
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 }
兩個具體產品的實現類,這裏只給出A的兩個具體產品類,B與此相似。app
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 }
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 }
在看抽象工廠模式以前,咱們先用工廠方法模式試一下。ide
以模擬更換數據庫爲例,UML圖以下:sqlserver
這裏咱們先只對User表進行操做,因此工廠方法只有CreateUser()。
定義一個建立訪問User表對象的抽象的工廠接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 5 }
用於客戶端訪問,解除與具體數據庫訪問的耦合。模擬插入方法insert。
1 public interface IUser { 2 3 public void insert(User user); 4 public User getUser(int id); 5 6 }
用於訪問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 }
用於訪問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 }
實現IFactory接口,實例化SqlserverFactory。
1 public class SqlserverFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new SqlserverUser(); 6 } 7 8 }
實現IFactory接口,實例化AccessFactory。
1 public class AccessFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new AccessUser(); 6 } 7 8 }
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圖以下:
先更改一下IFactory接口,增長一個建立訪問Department表對象的抽象的工廠接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 IDepartment createDepartment(); 5 6 }
增長一個IDepartment接口,用於客戶端訪問。
1 public interface IDepartment { 2 3 public void insert(Department department); 4 public Department getDepartment(int id); 5 6 }
在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 }
增長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 }
在客戶端中增長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,解除分支判斷帶來的耦合。