這里為了演示上簡單,假設:后台數據庫(暫為SqlServer只有用戶表User與部門表Department),各表字段相應精簡:
User(用戶表) | |
Id | 主鍵 |
Name | 姓名 |
DeptId | 部門編號 |
其余字段省略...... |
Department(部門表) | |
Id | 主鍵 |
Name | 名稱 |
Desc | 部門描述 |
其余字段省略...... |
后台數據庫:testdb的情況
建立相關的存儲過程:
一般我個人也喜歡ORM轉換成實體對象(見截圖)
(注意:這里增加了DeptTitle屬性<部門名稱>)
現在就是訪問數據庫SqlServer類型,封裝到SqlserverProvider中。如果將來訪問Access數據庫,對應訪問封裝到AccessProvider中。
(Provider這里表示數據訪問提供程序)
SqlUserProvier專門實現對SqlServer的表User的操作,AccessUserProvider專門實現對Access的表User的操作,很顯然,操作功能都相同(增刪改查<CRUD>),因而對不同子類的相同部分抽象出來,形成父類(UserProvider)。
開始着手具體子類實現:SqlUserProvider:UserProvider
(上圖數據庫連接串錯誤:單詞integrated才是正確的,最后面調試錯誤后改正。)
我們發現重載的GetUsers方法,大量代碼重復,進行方法重構(重復代碼重構為方法GetUsersFromReader)!
繼續具體實現父類的抽象方法:GetUserById,發現該方法的部分代碼與先前的GetUsersFromReader方法中的部分代碼又重復了!
發現上圖紅色部分重復(該圖GetUserById方法忘記傳遞存儲過程所需的參數了),再接着方法重構,提煉重復的代碼,避免以后改動的多次修改。
接着編寫該類后續的方法(增/刪/改):(可以打開VS開發環境中的<服務器資源管理器>,連接上對應的數據庫后,看存儲過程的參數,以免編碼遺忘傳參)
接着也來看看 類:AccessUserProvider,見下圖
上圖GetUsers方法中的查詢語句沒有聯合查詢,后續會改動。(這里僅僅示范,其似Access是可以類似建立查詢表<后台調用類似存儲過程方式>)
我們發現UserProvider的兩個子類的方法GetUserFromReader和GetUsersFromReader有重復代碼(僅僅是方法的參數不同) [想辦法抽象出來,放在父類中]
而方法的參數雖然是SqlDataReader與OleDbDataReader,但是查看定義,看到它們有自己的父類:DbDataReader。
public class SqlDataReader : DbDataReader, IDataReader, IDisposable, IDataRecord
public sealed class OleDbDataReader : DbDataReader
改寫父類:UserProvider
父類的方法加上修飾符protected,是為了確保只有子類能夠訪問。
子類便可以直接調用父類的方法了(GetUserFromReader和GetUsersFromReader方法),見截圖:
類似的完善SqlDepartmentProvider類和AccessDepartmentProvider類的代碼
(父類:DepartmentProvider提供保護方法GetDeparmentFromReader和GetDepartmentsFromReader)
每個具體的子類Provider都重復了屬性:ConnString,所以決定建一個父類:DataAccess來存放該屬性(UserProvider與DepartProvider都繼承自它),實際上DataAccess還可以包含其它的屬性和共用方法。
1 namespace 抽象工廠模式.DAL
2 {
3 public abstract class DataAccess
4 {
5 private string _connString ="";
6
7 public string ConnString
8 { get { return _connString; } }
9 }
10 }
public abstract class UserProvider:DataAccess
public abstract class DepartmentProvider:DataAccess
通常:數據庫連接串的內容都是存儲在對應的配置文件中,而不硬編碼。
桌面應用程序—[app.config],web應用程序---[web.config],這里以app.config示例,數據庫連接串先按照SqlServer數據庫訪問的。

1 <?xml version="1.0" encoding="utf-8" ?>
2 <configuration>
3 <connectionStrings>
4 <add name="DBConnString"
5 connectionString="SERVER=.\sqlexpress;DATABASE=testdb;INTEGRATED SECURITY=true"/>
6 </connectionStrings>
7 </configuration>
一定要手動引用:System.configuration,然后通過ConfigurationManager類來訪問連接串。
可以想象,根據數據庫的類型不同,實際底層操控的數據提供程序為Sql__Provider或是Access__Provider。
但對於用戶調用者(業務邏輯層)只需要操控Provider就可以了。
假設我所在城市有兩個行政分區(東一區和西二區),有一家“真不錯”總店[經營快餐系列的]在這兩個區都有連鎖店,對外統一電話:1111777。
(設一個總機號碼當然方便了,總不至於將來開了10家分店,對外公布10個電話號碼,誰能記住啊?)
比如說:我現在餓了,想吃這家提供的“經濟型快餐(一素<炒萵苣>一湯<豆腐湯>)”,我只要打電話111177,那邊只需要了解我的地址就可以了。(可以想象:知道了我的地址<就能明白所在行政區,然后公司總店去指派所在區的分店來服務>),對於客戶我而言:如何指派哪家分店來服務,以及經濟型快餐如何制作的,我都不會關心的。我只關心:要好吃,然后要快點(畢竟,餓太久會受不了的。)
回到我們的程序:
UserProvider好比一個物品蔬菜<萵苣>,DepartmentProvider好比湯菜<豆腐>。Access文件夾[經濟型],SqlServer文件夾[商務型] (你會問一個題外問題:有葷菜嗎?我的回答是:盡量別吃,如今都是激素喂出來的<現在人們消耗太快了,以前自然方式半年才能長大的動物,如今1個月人工方式就用激素喂成了>。吃多了,身體容易得病)。
只有一個問題:既然BLL(相對於DAL就是客戶調用者)只認(UserProvider/DepartmentProvider),又是如何調用實際其作用的子類呢?
這就需要用到設計模式中的<簡單工廠模式> (具體選擇哪個子類實際上用父類來完成<根據客戶配置需求>)
當然這里的配置文件:數據庫連接串和providerType需要匹配好。
父類:UserProvider我們提供靜態的Instance,來決定實際的子類(SqlUserProvider或者AccessUserProvider,根據配置文件的ProviderType的value來定)
如果將來出現了OracleUserProvider/DB2UserProvider/MySqlUserProvider/XmlUserProvider,這個藍色框框仍然需要增加case分支。這就不好了,需要再編碼(修改),好的設計方式應該是對擴展開放,對修改封閉。而且這里羅列出了所有的具體子類Provider,其實只需要一個子類Provider,但是其他的子類Provider也被迫出現在一起<大雜燴>(其實子類之間出現了耦合) 所以這種方式不可取,需要解決。
這里用反射的方式來解決這個問題。
首先約束:ProviderType的賦值需要規范,只能從(Sql/Access/DB2/MySql/Xml)選擇一個呢。可以發現:實際的子類名:ProviderType的值+“UserProvider”。
1 static public UserProvider Instance
2 {
3 get
4 {
5 if (_instance == null)
6 {
7 string providerTypeName=ConfigurationManager.AppSettings["ProviderType"]
8 +"UserProvider";
9 _instance =
10 Activator.CreateInstance(Type.GetType(providerTypeName)) as UserProvider;
11 }
12 return _instance;
13 }
14 }
如果:你的DAL是單獨用程序集方式建立的項目(類庫),請使用Assembly.Load等方式,這里由於是以文件夾方式組織的(DAL文件夾)<用Activator.CreateInstance可以OK.>
以后客戶端(BLL)調用的時候:比如刪除用戶表的記錄,就可以如下調用了:
UserProvider.Instance.DeleteUser(id)了。//這里BLL已經不知道是由哪個子類(如SqlUserProvider)來實際工作的。
進行一下測試,看是否運行正常!
發現錯誤:一:數據庫連接串需要修改:
二:文件夾SqlServer改成Sql。以前的命名空間對應改動下:
namespace 抽象工廠模式.DAL.Provider.Sql
{
public class SqlDepartmentProvider:DepartmentProvider
………………………………………….
namespace 抽象工廠模式.DAL.Provider.Sql
{
public class SqlUserProvider:UserProvider
………………………………………………………….
三:Type.GetType(需要完整的限定名)
測試通過:但發現沒有DeptTitle數據,查找錯誤發現
public User(int id, string name, int deptId, string deptTitle)
{
this.Id = id;
this.Name = name;
this.DeptId = deptId;
this.DeptTitle = deptTitle; //DeptTitle;
}
附上:AccessUserProvider的代碼如下:

1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Data;
5 using System.Data.OleDb;
6 using 抽象工廠模式.DAL;
7 using 抽象工廠模式.DAL.Entity;
8 namespace 抽象工廠模式.DAL.Provider.Access
9 {
10 public class AccessUserProvider:UserProvider
11 {
12
13 public override List<User> GetUsers()
14 {
15 using (OleDbConnection conn = new OleDbConnection(ConnString))
16 {
17 var dbcmd = conn.CreateCommand();
18 dbcmd.CommandText = "GetUsers";
19 dbcmd.CommandType = CommandType.StoredProcedure;
20 conn.Open();
21 return GetUsersFromReader(dbcmd.ExecuteReader());
22 }
23 }
24
25 public override List<User> GetUsers(int deptId)
26 {
27 using (OleDbConnection conn = new OleDbConnection(ConnString))
28 {
29 var dbcmd = conn.CreateCommand();
30 dbcmd.CommandText = "GetUsersByDepartmentId";
31 dbcmd.CommandType = CommandType.StoredProcedure;
32 dbcmd.Parameters.Add("@DeptId", OleDbType.Integer).Value = deptId;
33 conn.Open();
34 return GetUsersFromReader(dbcmd.ExecuteReader());
35 }
36 }
37
38 public override User GetUserById(int id)
39 {
40 using (OleDbConnection conn = new OleDbConnection(ConnString))
41 {
42 var dbcmd = conn.CreateCommand();
43 dbcmd.CommandText = "GetUserById";
44 dbcmd.CommandType = CommandType.StoredProcedure;
45 dbcmd.Parameters.Add("@Id", OleDbType.Integer).Value =id ;
46 conn.Open();
47 return GetUserFromReader(dbcmd.ExecuteReader());
48 }
49 }
50
51 public override bool DeleteUser(int id)
52 {
53 using (OleDbConnection conn = new OleDbConnection(ConnString))
54 {
55 OleDbCommand cmd = new OleDbCommand(
56 "delete from [user] where Id=" + id, conn);
57 conn.Open();
58 return cmd.ExecuteNonQuery() == 1;
59 }
60 }
61
62 public override int InsertUser(User user)
63 {
64 using (OleDbConnection conn = new OleDbConnection(ConnString))
65 {
66 OleDbCommand cmd = new OleDbCommand(
67 @"insert into [user](name,deptId) values(@id,@name);
68 select max(id) from [user] as newid",conn);
69 cmd.Parameters.Add("@id", OleDbType.Integer).Value = user.Id;
70 cmd.Parameters.Add("@name", OleDbType.VarChar).Value = user.Name;
71 conn.Open();
72 return (int)cmd.ExecuteScalar();
73 }
74 }
75
76 public override bool UpdateUser(User user)
77 {
78 using (OleDbConnection conn = new OleDbConnection(ConnString))
79 {
80 OleDbCommand cmd = new OleDbCommand(
81 "update [user] set name=@name,deptId=@deptid where Id=@id" , conn);
82 cmd.Parameters.Add("@name", OleDbType.Integer).Value = user.Name;
83 cmd.Parameters.Add("@deptid", OleDbType.Integer).Value = user.DeptId;
84 cmd.Parameters.Add("@id", OleDbType.Integer).Value = user.Id;
85 conn.Open();
86 return cmd.ExecuteNonQuery() == 1;
87 }
88 }
89 }
90 }
后台的testdb.mdb截圖: