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

1、什麼是抽象工廠模式

       抽象工廠模式其實就是多個工廠方法模式,好比前面工廠方法模式中,咱們建立多個不一樣類型的數據庫,有MySQL、SQLServer等等,就是用工廠方法模式來實現的,但此時咱們只能實現一個表(具體內容見下方工廠模式的實現),咱們數據庫中固然不可能只有一個表呀,因此抽象工廠模式就來了。html

  抽象工廠模式(Abstract Factory),提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。UML結構圖以下:git

       其中,AbstractFactory是抽象工廠接口,裏面包含全部的產品建立的抽象方法;ConcreteFactory則是具體的工廠,建立具備特定實現的產品對象;AbstractProduct是抽象產品,有可能由兩種不一樣的實現;ConcreteProduct則是對於抽象產品的具體分類的實現。spring

       抽象工廠模式是工廠方法模式的升級版本,在有多個業務品種、業務分類時,經過抽象工廠模式產生須要的對象是一種很是好的解決方式。下面是抽象工廠模式的通用源代碼類圖:sql

    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同理。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 }

    4. ConcreteProduct類

        兩個具體產品的實現類,這裏只給出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  }

  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 }

 2、抽象工廠模式的應用

    1. 什麼時候使用

  • 系統的產品有多於一個的產品族,而系統只消費其中某一族的產品時。

    2. 優勢

  • 封裝性,易於產品交換。因爲具體工廠類在一個應用中只需在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得很是容易,只需改變具體工程便可使用不一樣的產品配置。
  • 建立實例過程與客戶端分離。

    3. 缺點

  • 產品族擴展很是困難,改動或增長一個產品需同時改動多個類。

    4. 使用場景

  • 一個對象族(或一組沒有任何關係的對象)都有相同的約束。

    5. 應用實例

  • 生成不一樣操做系統的程序。
  • QQ換皮膚,一整套一塊兒換。
  • 更換數據庫。

3、工廠方法模式的實現

       在看抽象工廠模式以前,咱們先用工廠方法模式試一下。ide

       以模擬更換數據庫爲例,UML圖以下:sqlserver

       這裏咱們先只對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事先根本不知道是在訪問哪一個數據庫,卻能夠在運行時很好地完成工做,這就是所謂的業務邏輯與數據訪問的解耦。

       具體工廠方法模式可參考以前的文章:簡說設計模式——工廠方法模式

4、抽象工廠模式的實現

       上面用工廠方法模式實現了模擬更換數據庫,但數據庫中不可能只存在一個表,若是有多個表的狀況又該如何呢?咱們試着再增長一個表,好比增長一個部門表(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又是兩個不一樣的分類,解決這種涉及到多個產品系列的問題,就用到了抽象工廠模式。

5、利用反射實現數據訪問程序

       在上述的兩種模式中,咱們是有多少個數據庫就要建立多少個數據庫工廠,而咱們數據庫工廠中的代碼基本上是相同的,這時就可使用簡單工廠模式+抽象工廠模式來簡化操做,也即將工廠類及工廠接口拋棄,取而代之的是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

相關文章
相關標籤/搜索