舉個慄子 問題描述 模擬訪問資料庫“新增用戶”和“得到用戶”,用戶類假設只有 ID和Name 兩個欄位。 簡單實現 User SqlServerUser 測試 測試結果 存在問題 如果需要連接別的資料庫,那麼這個寫法無法擴展,下麵使用 工廠方法模式 實現 工廠方法模式實現 IUser SqlServ ...
舉個慄子
問題描述
模擬訪問資料庫“新增用戶”和“得到用戶”,用戶類假設只有ID和Name兩個欄位。
簡單實現
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表一條記錄
抽象工廠模式
定義
提供一個創建一系列相關或相互依賴對象的介面,而無需指定它們具體的類。
UML圖
代碼實現
實際上上面的修改實現已經滿足抽象工廠模式的實現方式,此處不再舉例。
優缺點
優點
- 最大的好處便是易於交換產品系列,由於不同的具體工廠類,在一個應用中只需要在初始化到時候出現一次,這就使得改變一個應用的具體工廠變得非常容易,它只需要改變具體工廠即可使用不同的產品配置。
- 它讓具體的創建實例改成與客戶端分離,客戶端是通過它們的抽象介面操縱實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶代碼中。
缺點
如果還要添加對項目表(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 分支了。
用配置文件+反射+抽象工廠實現
配置文件(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 (如果新增的具體訪問類名稱為 OracleUser 和 OracleDepartment 的話)即可達到目的,客戶端也不需要任何修改。
反射的好處
所有在用簡單工廠的地方,都可以考慮用反射技術來去除 switch 或 if,解除分支判斷帶來的耦合。
總結
可以發現到目前為止,就“工廠”而言,已經包含了三種設計模式:
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
對上述模式的不同點將在後續推出,本文篇幅已經過長,此處先不敘述。