簡說設計模式——抽象工廠模式


一、什么是抽象工廠模式

       抽象工廠模式其實就是多個工廠方法模式,比如前面工廠方法模式中,我們創建多個不同類型的數據庫,有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,解除分支判斷帶來的耦合。

 

       源碼地址:https://gitee.com/adamjiangwh/GoF


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM