大話設計模式筆記(十二)の抽象工廠模式

舉個栗子

問題描述

模擬訪問數據庫「新增用戶」和「獲得用戶」,用戶類假設只有ID和Name兩個字段。sql

簡單實現

User

/**
 * 用戶類
 * Created by callmeDevil on 2019/7/28.
 */
public class User {

    private int id;
    private String name;

    // 省略 get set 方法

}

SqlServerUser

/**
 * 假設sqlServer 鏈接,用於操做User表
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerUser {

    public void insert(User user){
        System.out.println("在SQL Server中給User表增長一條記錄");
    }

    public User getUser(int id){
        System.out.println("在SQL Server中根據ID獲得User表一條記錄");
        return null;
    }

}

測試

public class Test {

    public static void main(String[] args) {
        User user = new User();
        SqlServerUser su = new SqlServerUser();
        su.insert(user);
        su.getUser(user.getId());
    }

}

測試結果

在SQL Server中給User表增長一條記錄
在SQL Server中根據ID獲得User表一條記錄

存在問題

若是須要鏈接別的數據庫,那麼這個寫法沒法擴展,下面使用工廠方法模式實現數據庫

工廠方法模式實現

IUser

/**
 * 用於客戶端訪問,解除與具體數據庫訪問的耦合
 * Created by callmeDevil on 2019/7/28.
 */
public interface IUser {
    void insert(User user);
    User getUser(int id);
}

SqlServerUser

/**
 * 用於訪問SQL Server 的User
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在SQL Server中給User表增長一條記錄");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在SQL Server中根據ID獲得User表一條記錄");
        return null;
    }

}

AccessUser

/**
 * 用於訪問Access 的User
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessUser implements IUser {

    @Override
    public void insert(User user) {
        System.out.println("在Access 中給User表增長一條記錄");
    }

    @Override
    public User getUser(int id) {
        System.out.println("在在Access中根據ID獲得User表一條記錄");
        return null;
    }

}

IFactory

/**
 * 定義一個建立訪問User 表對象的抽象工廠接口
 * Created by callmeDevil on 2019/7/28.
 */
public interface IFactory {
    IUser createUser();
}

SqlServerFactory

/**
 * 實現IFactory 接口,實例化SQLServerUser
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }
}

AccessFactory

/**
 * 實現IFactory 接口,實例化AccessUser
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessFactory implements IFactory {
    @Override
    public IUser createUser() {
        return new AccessUser();
    }
}

測試

public class Test {
    public static void main(String[] args) {
        User user = new User();
        // 若要更改爲 Access 數據庫,只須要將此處改爲
        // IFactory factory = new AccessFactory();
        IFactory factory = new SqlServerFactory();
        IUser iUser = factory.createUser();
        iUser.insert(user);
        iUser.getUser(1);
    }
}

測試結果同上。編程

增長需求

若是要增長一個部門表(Department),須要怎麼改?設計模式

修改實現

Department

/**
 * 部門表
 * Created by callmeDevil on 2019/7/28.
 */
public class Department {

    private int id;
    private String name;

    // 省略 get set 方法

}

IDepartment

/**
 * 用於客戶端訪問,解除與具體數據庫訪問的耦合
 * Created by callmeDevil on 2019/7/28.
 */
public interface IDepartment {
    void insert(Department department);
    Department getDepartment(int id);
}

SqlServerDepartment

/**
 * 用於訪問SqlServer 的Department
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerDepartment implements IDepartment {

    @Override
    public void insert(Department department) {
        System.out.println("在 SqlServer 中給Department 表增長一條記錄");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在SQL Server中根據ID獲得Department表一條記錄");
        return null;
    }

}

AccessDepartment

/**
 * 用於訪問Access 的Department
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessDepartment implements IDepartment {

    @Override
    public void insert(Department department) {
        System.out.println("在Access 中給Department 表增長一條記錄");
    }

    @Override
    public Department getDepartment(int id) {
        System.out.println("在Access 中根據ID獲得Department表一條記錄");
        return null;
    }

}

IFactory

/**
 * 定義一個建立訪問User 表對象的抽象工廠接口
 * Created by callmeDevil on 2019/7/28.
 */
public interface IFactory {
    IUser createUser();
    IDepartment createDepartment(); //增長的接口方法
}

SqlServerFactory

/**
 * 實現IFactory 接口,實例化SQLServerUser
 * Created by callmeDevil on 2019/7/28.
 */
public class SqlServerFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new SqlServerUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new SqlServerDepartment(); //增長了SqlServerDepartment 工廠
    }

}

AccessFactory

/**
 * 實現IFactory 接口,實例化AccessUser
 * Created by callmeDevil on 2019/7/28.
 */
public class AccessFactory implements IFactory {

    @Override
    public IUser createUser() {
        return new AccessUser();
    }

    @Override
    public IDepartment createDepartment() {
        return new AccessDepartment(); //增長了AccessDepartment 工廠
    }

}

測試

public class Test {
    public static void main(String[] args) {
        User user = new User();
        Department dept = new Department();
        // 只需肯定實例化哪個數據庫訪問對象給 factory
        IFactory factory = new AccessFactory();
        // 則此時已於具體的數據庫訪問解除了依賴
        IUser iUser = factory.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        IDepartment iDept = factory.createDepartment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

測試結果

在Access 中給User表增長一條記錄
在Access 中根據ID獲得User表一條記錄
在Access 中給Department 表增長一條記錄
在Access 中根據ID獲得Department表一條記錄

抽象工廠模式

定義

提供一個建立一系列相關或相互依賴對象的接口,而無需指定它們具體的類。ide

UML圖

代碼實現

實際上上面的修改實現已經知足抽象工廠模式的實現方式,此處再也不舉例。sqlserver

優缺點

優勢

  • 最大的好處即是易於交換產品系列,因爲不一樣的具體工廠類,在一個應用中只須要在初始化到時候出現一次,這就使得改變一個應用的具體工廠變得很是容易,它只須要改變具體工廠便可使用不一樣的產品配置
  • 讓具體的建立實例改爲與客戶端分離,客戶端是經過它們的抽象接口操縱實例,產品的具體類名也被具體工廠的實現分離,不會出如今客戶代碼中

缺點

若是還要添加對項目表(Project)的訪問,那麼須要增長三個類,IProject、SQLServerProject、AccessProject,還須要更改 IFactory、ISQLServerFactory、AccessFactory 才能夠徹底實現,這太糟糕了。編程是門藝術,這樣大批量的改動,顯然是很是醜陋的作法。測試

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

去除IFactory、SQLServerFactory、AccessFactory,改成一個 DataAccess,用一個簡單工廠模式來實現。設計

結構圖

代碼實現

DataAccess

/**
 * 統一管理數據庫訪問
 * Created by callmeDevil on 2019/7/28.
 */
public class DataAccess {

    // 數據庫名稱,可替換成 Access
    private static final String DB = "SqlServer";
//    private static final String DB = "Access";

    public static IUser createUser() {
        IUser user = null;
        switch (DB) {
            case "SqlServer":
                user = new SqlServerUser();
                break;
            case "Access":
                user = new AccessUser();
                break;
            default:
                break;
        }
        return user;
    }

    public static IDepartment createDepartment() {
        IDepartment department = null;
        switch (DB) {
            case "SqlServer":
                department = new SqlServerDepartment();
                break;
            case "Access":
                department = new AccessDepartment();
                break;
            default:
                break;
        }
        return department;
    }

}

測試

public class Test {
    public static void main(String[] args) {
        User user = new User();
        Department dept = new Department();
        // 直接獲得實際的數據庫訪問實例,而不存在任何的依賴
        IUser iUser = DataAccess.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        IDepartment iDept = DataAccess.createDepartment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

測試結果

在SQL Server中給User表增長一條記錄
在SQL Server中根據ID獲得User表一條記錄
在SQL Server中給Department 表增長一條記錄
在SQL Server中根據ID獲得Department表一條記錄

存在問題

雖然解決了抽象工廠模式中須要修改太多地方的問題,但又回到了簡單工廠模式一開始的問題了,就是若是要鏈接 Oracle 數據庫,那麼須要修改的地方則是 DataAccess 類中全部方法的 swicth 中加 case 分支了。code

用配置文件+反射+抽象工廠實現

配置文件(db.properties)

# 數據庫名稱,可更改爲 Access
db=SqlServer

DataAccess

/**
 * 統一管理數據庫訪問
 * Created by callmeDevil on 2019/7/28.
 */
public class DataAccess {

    // 數據庫名稱,從配置文件中獲取
    private static String DB;

    public static IUser createUser() throws Exception {
        if (DB == null || DB.trim() == "") {
            return null;
        }
        // 拼接具體數據庫訪問類的權限定名
        String className = "com.xxx." + DB + "User";
        return (IUser) Class.forName(className).newInstance();
    }

    public static IDepartment createDeptment() throws Exception {
        if (DB == null || DB.trim() == "") {
            return null;
        }
        // 拼接具體數據庫訪問類的權限定名
        String className = "com.xxx." + DB + "Department";
        return (IDepartment) Class.forName(className).newInstance();
    }

    public static String getDB() {
        return DB;
    }

    public static void setDB(String DB) {
        DataAccess.DB = DB;
    }

}

測試

public class Test {
    public static void main(String[] args) throws Exception {
        // 加載配置文件
        Properties properties = new Properties();
        InputStream is = new FileInputStream(new File("xxx\\db.properties")); // 配置文件所在路徑,當前方式採用絕對路徑獲取
        properties.load(is);
        is.close();
        String db = properties.getProperty("db");
        // 使用具體的數據庫告訴管理類
        DataAccess dataAccess = new DataAccess();
        dataAccess.setDB(db);

        User user = new User();
        IUser iUser = dataAccess.createUser();
        iUser.insert(user);
        iUser.getUser(1);

        Department dept = new Department();
        IDepartment iDept = dataAccess.createDeptment();
        iDept.insert(dept);
        iDept.getDepartment(1);
    }
}

測試結果

在SQL Server中給User表增長一條記錄
在SQL Server中根據ID獲得User表一條記錄
在SQL Server中給Department 表增長一條記錄
在SQL Server中根據ID獲得Department表一條記錄

如今若是咱們增長了 Oracle 數據庫訪問,相關類的增長是不可避免的,這點不管用任何辦法都解決不了,不過這叫擴展,開放-封閉原則告訴咱們,對於擴展,咱們開放,但對於修改,咱們應該儘可能關閉,就目前實現方式而言,只須要將配置文件中改成 Oracle (若是新增的具體訪問類名稱爲 OracleUserOracleDepartment 的話)便可達到目的,客戶端也不須要任何修改。server

反射的好處

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

總結

能夠發現到目前爲止,就「工廠」而言,已經包含了三種設計模式:

  • 簡單工廠模式
  • 工廠方法模式
  • 抽象工廠模式

對上述模式的不一樣點將在後續推出,本文篇幅已通過長,此處先不敘述。

相關文章
相關標籤/搜索