學習抽象工廠模式ing...
希望大家給出意見,有不對的地方大家多多指教!小女子在此謝過!
---------------------------------------------------------------------------
最基本的數據訪問程序
User類:表示數據庫中字段,假設只有ID和Name兩個字段

class User { private int _id; public int Id { get { return _id; } set { _id = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } }
SqlserverUser類:用於操作User表,假設只有“添加用戶”和“得到用戶”兩個方法(這里為了簡單,使用兩句話代替O(∩_∩)O~)

class SqlserverUser { public void Insert(User user) { Console.WriteLine("通過SQL Server給User表添加一條記錄"); } public User GetUser(int id) { Console.WriteLine("通過SQL Server根據ID得到User表中的一條記錄"); return null; }
客戶端代碼

static void Main(string[] args) { User user = new User(); SqlserverUser su = new SqlserverUser(); su.Insert(user); su.GetUser(1); Console.ReadKey(); }
在這個最基本數據訪問程序中,如果要更換數據庫,那么如果代碼量非常大的情況下,修改數據庫代碼是非常痛苦的,甚至與數據庫無關的代碼頁需要改。這里之所以不能更換數據庫,原因在於
SqlserverUser su = new SqlserverUser();
使得su這個對象被框死在SQL Server上了,如果這里是多態的,那么在執行
su.Insert(user); su.GetUser(1);
時,就不用考慮是在用SQL Server還是在用Access或者Oracle了。
使用工廠方法模式的數據訪問程序,解除客戶端與具體數據庫訪問的耦合
這里我們添加一個IUser接口,用來解除客戶端與具體數據庫訪問的耦合:
interface IUser { void Insert(User user); User GetUser(int id); }
這個時候我們就需要修改原先簡單的數據訪問代碼了,因為我們的目的是解除與具體數據訪問的耦合,所以需要讓數據訪問類來繼承這個接口(不管是SQL Server還是Access),然后通過工廠模式來創建訪問數據庫表User的對象。
首先修改SqlserverUser類,讓之繼承IUser接口;
class SqlserverUser:IUser
然后創建AccessUser類,用來訪問Access數據庫中的User類

class AccessUser:IUser { public void Insert(User user) { Console.WriteLine("通過Access給User表添加一條記錄"); } public User GetUser(int id) { Console.WriteLine("通過Access根據ID得到User表中的一條記錄"); return null; } }
接下來創建IFactory接口,定義一個創建訪問User表對象的抽象的工廠接口
interface IFactory { IUser CreateUser(); }
SqlserverFactory類,實現IFactory接口,用來實例化SqlserverUser
class SqlServerFactory:IFactory { public IUser CreateUser() { return new SqlserverUser(); } }
AccessFactory類,同樣實現IFactory接口,用來實例化AccessUser
class AccessFactory:IFactory { public IUser CreateUser() { return new AccessUser(); } }
修改后的客戶端代碼:
static void Main(string[] args) { User user = new User(); //IFactory factory = new SqlServerFactory(); IFactory factory = new AccessFactory(); //如果需要修改數據庫,只需要變更這句代碼即可 IUser su = factory.CreateUser(); su.Insert(user); su.GetUser(1); Console.ReadKey(); }
此時由於多態的關系,使得聲明IUser接口的對象su事先不知道是在訪問哪個數據庫,卻可以再運行時很好地完成工作,這就解決了所謂的業務邏輯與數據訪問的解耦
這個時候問題還沒有完全解決,數據庫中不可能只有一張User表,如果增加一張Department表,那么依照上面的方法,需要增加一個IDepartment接口;
然后由AccessDepartment和SqlServerDepartment兩個類來實現這個接口,完成對數據庫的操作。
完成對數據庫的操作之后,就需要在對應的數據庫工廠中創建相應的對象,因為所有的數據庫工廠都實現了IFactory接口,所以我們只需要在對應的數據庫工廠中添加相應的方法。

class SqlServerFactory:IFactory { public IUser CreateUser() { return new SqlserverUser(); } public IDepartment CreateDepartment() { return new SqlServerDepartment(); } }

class AccessFactory:IFactory { public IUser CreateUser() { return new AccessUser(); } public IDepartment CreateDepartment() { return new AccessDepartment(); } }
這樣一來雖然解決了業務邏輯和數據訪問的緊耦合問題,但是如果數據庫中的數據量非常龐大的時候,我們也要一張表一個接口的這樣做嗎?當然不,當只有一個User表的時候,只需要一個User類和User操作類,這時使用工廠方法模式來解決問題;但是現在一般情況下顯然數據庫中有很多的表,而且SQL Server和Access又是兩大不同的分類,所以解決涉及到多個產品系列的問題,就有一種專門的工廠模式:抽象工廠模式。
抽象工廠模式
Abstract Factory,提供一個創建一系列相關或相互依賴的接口,而無需指定它們具體的類。
優點:
1、便於交換產品系列,由於具體工廠類,比如IFactory factory = new SqlServerFactory(),在一個應用中只需要在初始化的時候出現一次,這就使得改變一個應用的具體工廠變的非常容易,它只需要改變具體工廠即可使用不同的產品配置;
2、它讓具體的創建實例過程與客戶端分離,客戶端是通過它們的抽象接口操作實例,產品的具體類名也被具體工廠的實現分離,不會出現在客戶端代碼中。
缺點:
1、如果有增加功能的需求,比如增加表Project,那么至少需要增加三個類:IProject、SqlServerProject、AccessProject,同時還需要更改IFactory、SqlServerFactory、AccessFactory才能完全實現。所以增加功能時是非常麻煩的。
2、客戶端程序多的情況下(在這里是Main里面的代碼),在每一個類的開始都要聲明IFactory factory = new SqlServerFactory(),如果有100個調用數據庫訪問的類,改為iAccess時就需要修改至少100處代碼。
為了解決上面缺點2的問題,我們試着用簡單工廠來代替抽象工廠;
不再使用IFactory、SqlServerFactory、AccessFactory,替換為DataAccess,用一個簡單工廠來實現:

class DataAccess { //private static readonly string db = "SqlServer"; private static readonly 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; } }
客戶端代碼:

static void Main(string[] args) { User user = new User(); Department dept = new Department(); //IFactory factory = new SqlServerFactory(); //IFactory factory = new AccessFactory(); //IUser su = factory.CreateUser(); IUser su = DataAccess.CreateUser(); su.Insert(user); su.GetUser(1); //IDepartment id = factory.CreateDepartment(); IDepartment id = DataAccess.CreateDepartment(); id.Insert(dept); id.GetDepartment(1); Console.ReadKey(); }
通過上面的方法在客戶端沒有出現一個SQL Server或者Access的字樣,達到了解耦的目的。
但是如果需要使用Oracle數據庫訪問,就需要在每個switch中增加case了。
用反射+配置文件+抽象工廠實現數據訪問程序
在增加數據庫時,相關類的增加是不可避免的,這點目前為止還不能解決,不過開放-封閉原則告訴我們:對於擴展,我們開放;對於修改,我們應該盡量關閉。
因此我們可以通過反射,來獲得當前運行的程序集名稱,再通過拼接字符串得到所需要創建的實例的類名,代碼如下:
private static readonly string db = ConfigurationManager.AppSettings["DB"]; private static readonly string assemblyName = Assembly.GetExecutingAssembly().GetName().Name; public static IUser CreateUser() { string className = assemblyName + "." + db + "User"; return (IUser)Assembly.Load(assemblyName).CreateInstance(className); } public static IDepartment CreateDepartment() { string className = assemblyName + "." + db + "Department"; return (IDepartment)Assembly.Load(assemblyName).CreateInstance(className); }
其中我把相關的數據庫字符串放在了配置文件中:
<appSettings> <add key="DB" value="Access"/> </appSettings>
這樣就解決了關於抽象工廠數據訪問的可維護可擴展的問題。
--------------------------------------------------------------------
各位多多指導...Thank you!