抽象工廠模式(十一)

由一個例子引出抽象工廠模式:

在實際開發中,我們有時會遇到切換數據庫的需求,由於數據庫之間的差異,不同數據庫對數據表的增刪改查操作也有差異。爲了解決這個問題,我們很容易想到用工廠模式來實現:

User類:

public class User {
    private Integer id;
    // get set 方法...
}

IUser接口:用於客戶端訪問,解除與具體數據庫訪問的耦合。

public interface IUser {

    void insert(User user);

    User getUser(int id);
}

SqlServerUser類:用於訪問SqlServer的User。

public class SqlServerUser implements IUser {
    @Override
    public void insert(User user) {
        System.out.println("sql server 數據庫新增用戶記錄");
    }

    @Override
    public User getUser(int id) {
        System.out.println("sql server 數據庫獲取用戶記錄");
        return null;
    }
}

MySqlUser類:用於訪問MySQL的User。

public class MySqlUser implements IUser {
    @Override
    public void insert(User user) {
        System.out.println("mysql 新增用戶");
    }

    @Override
    public User getUser(int id) {
        System.out.println("mysql 獲取用戶");
        return null;
    }
}

IFactory接口:定義一個創建訪問User表對象的抽象工廠接口。

public interface IFactory {
    /**
     * 創建用戶增刪改查的實現類
     * @return
     */
    IUser createUser();
}

SqlServerFactory類:實現IFactory接口,實例化SqlServerUser。

public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

MySqlFactory類:實現IFactory接口,實例化MySqlUser。

public class MySqlFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new MySqlUser();
    }
}

客戶端代碼:

public class AbstractFactoryTest {
    public static void main(String[] args) {
        IFactory factory = new SqlServerFactory();
        // 如果需要切換MySQL數據庫,只需要將上面的代碼換成下面的即可
//        IFactory factory = new MySqlFactory();
        IUser user = factory.createUser();
        user.insert(new User());
        user.getUser(1);
    }
}

輸出結果:

sql server 數據庫新增用戶記錄
sql server 數據庫獲取用戶記錄

現在如果要換數據庫,只需要將new SqlServerFactory()改成new MySqlFactory()即可,這就實現了業務邏輯與數據訪問的解耦。

這時如果要增加一個部門表(Department表)此時應該怎麼辦呢?這時我們需要增加幾個類:

Department類:

public class Department {
    private Integer id;
}

IDepartment接口:用於客戶端訪問,解除與具體數據庫訪問的耦合:

public interface IDepartment {
    void insert(Department department);
    void getDepartment(int id);
}

SqlServerDepartment類:用於訪問SqlServer的Department。

public class SqlServerDepartment implements IDepartment {
    @Override
    public void insert(Department department) {
        System.out.println("SqlServer新增 Department 記錄");
    }

    @Override
    public void getDepartment(int id) {
        System.out.println("SqlServer獲取 Department 記錄");
    }
}

MySqlDepartment類:用於訪問MySql的Department。

public class MySqlDepartment implements IDepartment {
    @Override
    public void insert(Department department) {
        System.out.println("MySQL新增 Department 記錄");
    }

    @Override
    public void getDepartment(int id) {
        System.out.println("MySQL獲取 Department 記錄");
    }
}

IFactory接口:定義一個創建訪問Department表對象的抽象工廠類接口。

public interface IFactory {
    /**
     * 創建用戶增刪改查的實現類
     * @return
     */
    IUser createUser();

    // 增加的接口方法
    IDepartment createDepartment();
}

SqlServerFactory類:實現IFactory接口,實例化SqlServerUser和SqlServerDepartment。

public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment();
    }
}

MySqlFactory類:實現IFactory接口,實例化MySqlUser和MySqlDepartment。

public class MySqlFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new MySqlUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new MySqlDepartment();
    }
}

客戶端代碼:

public class AbstractFactoryTest {
    public static void main(String[] args) {
        IFactory factory = new SqlServerFactory();
        // 如果需要切換MySQL數據庫,只需要將上面的代碼換成下面的即可
//        IFactory factory = new MySqlFactory();
        IUser user = factory.createUser();
        user.insert(new User());
        user.getUser(1);

        IDepartment department = factory.createDepartment();
        department.insert(new Department());
        department.getDepartment(1);
    }
}

輸出:

sql server 數據庫新增用戶記錄
sql server 數據庫獲取用戶記錄
SqlServer新增 Department 記錄
SqlServer獲取 Department 記錄

這樣就實現增加一個Department的操作了。只有一個User類和User操作類的時候,是只需要工廠方法模式的,但現在顯然數據庫中有很多的表,而SQL server與MySQL又是兩大不同的分類,所以解決這種涉及到多個產品系列的問題,有一個專門的工廠模式叫做抽象工廠模式。

抽象工廠模式:提供一個創建一系列相關或互相依賴對象的接口,而無需指定它們具體的類。

通常在運行的時刻再創建一個工廠類的實例,這個具體的工廠再創建具有特定實現的產品對象,也就是說,爲創建不同的產品對象,客戶端應使用不同的具體工廠。

抽象工廠模式的優點與缺點

工廠模式的最大好處就是易於交換產品系列,由具體工廠類,例如IFactory factory = new SqlServerFactory(),在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,他只需要改變具體工廠即可使用不同的配置。

第二大好處是,它讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操縱實例,產品具體類名也被具體工廠的實現分離,不會出現在客戶端代碼中。就像剛纔的例子,客戶端所認識的只有IUser和IDepartment,至於使用SQL server實現還是MySQL實現就不知道了。

當然,抽象工廠模式也有缺點,比如要增加一個Department表就要增加很多類,IDepartment,SqlServerDepartment,MySqlDepartment,還需要更改IFactory、SqlServerFactory和MySqlFactory纔可以完全實現。而且我們的客戶端程序不止一個,很多地方都會用到IUser和IDepartment,如果100個地方要切換數據庫就要改動100處代碼,如此大批量的改動顯然是非常醜陋的做法。

用簡單工廠來改進抽象工廠

去除IFactory、SqlServerFactory和MySqlFactory三個工廠類,取而代之的是DataAccess類,用一個簡單工廠實現:

public class DataAccess {
    private final static String DB = "MySql";
//    private final static String db = "SqlServer";
    public static IUser createUser(){
        IUser result = null;
        switch (DB){
            case "MySql":
                result = new MySqlUser();
                break;
            case "SqlServer":
                result = new SqlServerUser();
                break;
            default:
                break;
        }
        return result;
    }

    public static IDepartment createDepartment(){
        IDepartment result = null;
        switch (DB){
            case "MySql":
                result = new MySqlDepartment();
                break;
            case "SqlServer":
                result = new SqlServerDepartment();
                break;
            default:
                break;
        }
        return result;
    }
}

客戶端代碼:

public class SimpleFactoryTest {
    public static void main(String[] args) {
        IUser user = DataAccess.createUser();
        user.insert(new User());
        user.getUser(1);

        IDepartment department = DataAccess.createDepartment();
        department.insert(new Department());
        department.getDepartment(1);
    }
}

客戶端沒有出現一個SqlServer或者Mysql的字樣,實現了完全解耦。

上面的改動還存在不足之處,比如增加一個Oracle數據庫訪問,本來抽象工廠增加一個OracleFactory工廠類就可以了,現在就比較麻煩了,需要在DataAccess類的每個switch語句中加case。

反射+抽象工廠的數據訪問程序

我們可以通過反射進一步優化代碼,去除switch語句:

改變後的DataAccess:

public class DataAccess {

    private final static String BASE_PACKAGE_PATH = "com.nss.abstractfactory";
    private final static String DB = "MySql";
//    private final static String DB = "SqlServer";

    public static IUser createUser() throws Exception {
        String className = BASE_PACKAGE_PATH + "." + DB + "User";
        return (IUser) Class.forName(className).newInstance();
    }

    public static IDepartment createDepartment() throws Exception {
        String className = BASE_PACKAGE_PATH + "." + DB + "Department";
        return (IDepartment) Class.forName(className).newInstance();
    }
}

我們只需要事先指定需要創建的類的路徑就可以動態獲取到相應的內容,現在我們增加一個Oracle數據訪問,相關類的增加是避免不了的,我們對於擴展是開放的,就目前而言,只需要將DB改爲Oracle並增加相應的OracleUser以及OracleDepartment就可以了。

如果要增加Project產品時,只需要增加三個與Project相關的類,再修改DataAccess,在其中增加一個public static IProject createProject()方法就可以了。當然,關於DB的配置我們也可以放在配置文件中讀取,這樣代碼就不會做任何修改了。

所有使用簡單工廠的地方,都可以考慮用反射技術來去除switch或if,解除分支判斷帶來的耦合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章