大話設計模式之抽象工廠模式總結-java實現
注:示例來自《大話設計模式》
現有如下需求 寫一個基本的資料訪問程式 資料庫用SqlServer 簡單程式碼實現如下
使用者類
package Test15;
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
操作表類
package Test15;
public class SqlserverUser {
public void Insert(User user)
{
System.out.println("在Sqlserver中給User表增加一條記錄");
}
public User GetUser(int id)
{
System.out .println("在Sqlserver中根據ID得到User表一條記錄");
return null;
}
}
客戶端程式碼
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
SqlserverUser su = new SqlserverUser();
su.Insert(user);
su.GetUser(1);
}
}
上面的寫法 客戶端與SqlServer強耦合 如果想要更換資料庫為Access 該怎麼辦呢
下面使用工廠方法模式進行重構 程式碼如下
IUser介面
package Test15;
public interface IUser {
void Insert(User user);
User GetUser(int id);
}
SqlserverUser類
package Test15;
public class SqlserverUser implements IUser {
@Override
public void Insert(User user)
{
System.out.println("在Sqlserver中給User表增加一條記錄");
}
@Override
public User GetUser(int id)
{
System.out.println("在Sqlserver中根據ID得到User表一條記錄");
return null;
}
}
AccessUser類
package Test15;
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介面
package Test15;
public interface IFactory {
IUser CreateUser();
}
SqlServerFactory類
package Test15;
public class SqlServerFactory implements IFactory {
@Override
public IUser CreateUser() {
return new SqlserverUser();
}
}
AccessFactory類
package Test15;
public class AccessFactory implements IFactory {
@Override
public IUser CreateUser() {
return new AccessUser();
}
}
客戶端程式碼
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
//IFactory factory = new SqlServerFactory();
IFactory factory = new AccessFactory();
IUser iu = factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
}
}
現在如果要換資料庫 只需要把new SqlServerFactory()改成new AccessFactory()
但是問題還沒有完全解決 資料庫裡不可能只有一個User表 很可能有其他表 比如增加部門表 此時該怎麼辦呢 修改程式碼如下
部門類
package Test15;
public class Department {
private int id;
private String deptName;;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
IDepartment介面
package Test15;
public interface IDepartment {
void Insert(Department department);
Department GetDepartment(int id);
}
SqlserverDepartment類
package Test15;
public class SqlserverDepartment implements IDepartment {
public void Insert(Department department)
{
System.out.println("在Sqlserver中給Department表增加一條記錄");
}
public Department GetDepartment(int id)
{
System.out.println("在Sqlserver中根據ID得到Department表一條記錄");
return null;
}
}
AccessDepartment類
package Test15;
public class AccessDepartment implements IDepartment {
public void Insert(Department department)
{
System.out.println("在Access中給Department表增加一條記錄");
}
public Department GetDepartment(int id)
{
System.out.println("在Access中根據ID得到Department表一條記錄");
return null;
}
}
IFactory介面
package Test15;
public interface IFactory {
IUser CreateUser();
IDepartment CreateDepartment();
}
SqlServerFactory類
package Test15;
public class SqlServerFactory implements IFactory {
@Override
public IUser CreateUser() {
return new SqlserverUser();
}
@Override
public IDepartment CreateDepartment()
{
return new SqlserverDepartment();
}
}
AccessFactory類
package Test15;
public class AccessFactory implements IFactory {
@Override
public IUser CreateUser() {
return new AccessUser();
}
@Override
public IDepartment CreateDepartment()
{
return new AccessDepartment();
}
}
客戶端程式碼
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
Department dept = new Department();
//IFactory factory = new SqlServerFactory();
IFactory factory = new AccessFactory();
IUser iu = factory.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = factory.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
}
}
不知不覺間 我們已經重構出了抽象工廠模式
抽象工廠模式 提供一個建立一系列相關或相互依賴物件的介面 而無需指定它們具體的類
抽象工廠模式最大的好處便是易於交換產品系列 由於具體工廠類 例如IFactory factory = new AccessFactory() 在一個應用中只需要在初始化的時候出現一次 這就使得改變一個應用的具體工廠變得非常容易 它只需要改變具體工廠即可使用不同的產品配置
第二大好處是 它讓具體的建立例項過程與客戶端分離 客戶端是通過它們的抽象介面操縱例項 產品的具體類名也被具體工廠的實現分離 不會出現在客戶程式碼中
缺點 如果需要增加新的表 那麼就要新增三個類 並且要修改三個工廠類 這樣大批量的改動 顯然是非常醜陋的做法
下面我們用簡單工廠來改進抽象工廠 程式碼如下
DataAccess類
package Test15;
public class DataAccess {
private static final String db = "Sqlserver";
//private static final String db = "Access";
public static IUser CreateUser()
{
IUser result = null;
switch (db)
{
case "Sqlserver":
result = new SqlserverUser();
break;
case "Access":
result = new AccessUser();
break;
}
return result;
}
public static IDepartment CreateDepartment()
{
IDepartment result = null;
switch (db)
{
case "Sqlserver":
result = new SqlserverDepartment();
break;
case "Access":
result = new AccessDepartment();
break;
}
return result;
}
}
客戶端程式碼
package Test15;
public class Program {
public static void main(String[] args) {
User user = new User();
Department dept = new Department();
IUser iu = DataAccess.CreateUser();
iu.Insert(user);
iu.GetUser(1);
IDepartment id = DataAccess.CreateDepartment();
id.Insert(dept);
id.GetDepartment(1);
}
}
現在客戶端已經不再受改動資料庫訪問的影響了 但是如果我需要增加Oracle資料庫訪問 本來抽象工廠只增加一個OracleFactory工廠類就可以了 現在就比較麻煩了
下面我們用反射+抽象工廠的方式進行重構 程式碼如下
修改DataAccess類
package Test15;
public class DataAccess {
private static final String AssemblyName = "Test15";
private static final String db = "Sqlserver";
//private static final String db = "Access";
public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
String className = AssemblyName+"."+db+"User";
Class<IUser> forName = (Class<IUser>) Class.forName(className);
return forName.newInstance();
}
public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException
{
String className = AssemblyName+"."+db+"Department";
Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className);
return forName.newInstance();
}
}
現在如果增加Oracle資料訪問 我們只需更改private static final String db = “Sqlserver”;為private static final String db = “Oracle”;
但是 總感覺還是有點遺憾 因為在更換資料庫訪問時 我還是需要去改程式重編譯
下面我們用反射+配置檔案的方式進行重構
新增一個sql.properties檔案 內容如下
db=Sqlserver
DataAccess類
package Test15;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class DataAccess {
private static final String AssemblyName = "Test15";
public static String getDb() throws IOException {
Properties properties = new Properties();
String db = "";
// 使用BufferedReader載入properties配置檔案生成對應的輸入流
BufferedReader bufferedReader = new BufferedReader(new FileReader(System.getProperty("user.dir")+"/src/test/java/Test15/sql.properties"));
// 使用properties物件載入輸入流
properties.load(bufferedReader);
//獲取key對應的value值
db = properties.getProperty("db");
return db;
}
public static IUser CreateUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
{
String className = AssemblyName+"."+getDb()+"User";
Class<IUser> forName = (Class<IUser>) Class.forName(className);
return forName.newInstance();
}
public static IDepartment CreateDepartment() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException
{
String className = AssemblyName+"."+getDb()+"Department";
Class<IDepartment> forName = (Class<IDepartment>) Class.forName(className);
return forName.newInstance();
}
}
現在如果需要更換資料庫 只需要修改配置檔案就可以了 從這個角度上說 所有在用簡單工廠的地方 都可以考慮用反射技術來去除switch或if 解除分支判斷帶來的耦合