打造屬于你的提供者(Provider = Strategy + Factory Method)
1.1.1 摘要
在日常系統設計中,我們也許聽說過提供者模式,甚至幾乎每天都在使用它,在.NET Framkework 2.0中微軟提出了提供者模式(Provider),所以我們可以發(fā)現.NET Framkework中有很多類命名都含有“Provider”,例如:IFormatProvider,RSACryptoServiceProvider等等,由此可見它們都間接或直接使用了提供者模式這一設計思想,現在讓我們來介紹一下提供者模式(Provider)。
1.1.2 正文
首先讓我們通過提供者模式(Provider)結構圖,了解什么是提供者模式(Provider)。
圖1提供者模式(Provider)結構圖
通過上面的結構圖我們發(fā)現提供者模式(Provider),并沒有想象中的那么復雜而且整個結構就是使用了一些繼承關系而已。
提供者模式(Provider)的結構圖,和我們熟悉的策略模式(Strategy)結構基本一致就是通過繼承擴展不同種類的算法。
策略模式(Strategy):它定義了算法家族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化,不會影響到使用算法的客戶。
圖2策略模式(Strategy)結構圖
所以很多人都喜歡把提供者模式(Provider)和策略模式(Strategy)進行對比,更甚至有些人認為就是策略模式(Strategy),其實不然我們可以發(fā)現每種提供者它們都是繼承于一個基類ProviderBase,無論是系統還是自定義的提供者都必須繼承于它。
現在讓我們看看抽象類ProviderBase。
public abstract class ProviderBase { // Fields private string _Description; private bool _Initialized; private string _name; // Methods protected ProviderBase(); public virtual void Initialize(string name, NameValueCollection config); // Properties public virtual string Description { get; } public virtual string Name { get; } }
通過上面的代碼我們發(fā)現,ProviderBase只包含一些描述字段和屬性,還有就是一個無參構造函數和一個初始化方法Initialize(string name, NameValueCollection config)。該方法是提供者模式實現動態(tài)調用的核心方法(DI的實現)。
在ProviderBase中提供了Initialize方法實現和統一簽名,然后讓提供者重寫該方法,現在我們又有問題了究竟這個方法要實現什么功能呢?讓我們看看Initialize方法的實現吧!
public virtual void Initialize(string name, NameValueCollection config) { lock (this) { if (this._Initialized) { throw new InvalidOperationException( SR.GetString("Provider_Already_Initialized")); } this._Initialized = true; } if (name == null) { throw new ArgumentNullException("name"); } if (name.Length == 0) { throw new ArgumentException( SR.GetString("Config_provider_name_null_or_empty"), "name"); } this._name = name; if (config != null) { this._Description = config["description"]; config.Remove("description"); } }
其實這個方法功能很簡單就是去讀我們配置文件相應節(jié)點的值, 現在我們知道可以通過重新 Initialize 方法,可以實現從配置文件中讀取 Provider 的信息(如:name,type,connectionString 等)。我們都知道繼承使得類之間的耦合度增加, 這就是策略模式的一個缺點具體算法通過繼承來進行擴展。但我們看到提供者模式(Provider)使用了一種比較靈活方法對具體提供者進行擴展。
圖 3 ASP.NET中的提供者模式(Provider)
通過上圖我們發(fā)現.NET中的MembershipProvider,RoleProvider,SiteMapProvider等提供者都必須繼承于ProviderBase類,接著是具體實現提供者的類。除此之外,我們自定義提供者都必須繼承于ProviderBase類。
我們對于提供者模式(Provider)有了初步的認識,那么現在讓我們定義屬于自己的提供者吧!
假設我們要設計數據庫提供者,考慮到我們系統的靈活性和擴展性,我們應該使系統可以在不同數據之間無縫切換,由于數據庫的種類有:SqlServer,Oracle,MySql,SQLite等,首先定義一個抽象類DataProvider讓具體數據提供者來實現它。
我們定義一個DataProvider類繼承于ProviderBase,然后添加數據連接字符串,存儲過程名字和參數屬性,最后就是一系列的對數據庫操作方法。
/// <summary> /// Defines the methods that Data providers should be implemented. /// </summary> public abstract class DataProvider : ProviderBase { /// <summary> /// Runs the specified transaction. /// </summary> /// <param name="transaction">The transaction.</param> public abstract void Run(DbTransaction transaction); /// <summary> /// Runs the specified transaction. /// </summary> /// <param name="transaction">The transaction.</param> /// <param name="parameters">The stored procedure parameters.</param> public abstract void Run(DbTransaction transaction, DbParameter[] parameters); //public abstract DbDataReader Run(DbParameter[] parameters); /// <summary> /// Runs the specified connection string. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="parameters">The stored procedure parameters.</param> /// <returns>Returns dataset.</returns> public abstract DataSet Run(string connectionString, DbParameter[] parameters); /// <summary> /// Runs the scalar. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="parameters">The stored procedure parameters.</param> /// <returns>Returns an object.</returns> public abstract object RunScalar(string connectionString, DbParameter[] parameters); /// <summary> /// Runs the scalar. /// </summary> /// <param name="transaction">The transaction.</param> /// <param name="parameters">The stored procedure parameters.</param> /// <returns></returns> public abstract object RunScalar(SqlTransaction transaction, DbParameter[] parameters); /// <summary> /// Runs the specified connectionstring. /// </summary> /// <param name="connectionstring">The connectionstring.</param> /// <returns></returns> public abstract DataSet Run(string connectionstring); /// <summary> /// Runs this instance. /// </summary> public abstract void Run(); /// <summary> /// Runs the specified parameters. /// </summary> /// <param name="parameters">The stored procedure parameters.</param> /// <returns></returns> public abstract DataSet Run(DbParameter[] parameters); /// <summary> /// Runs the specified command type. /// </summary> /// <param name="commandType">Type of the command.</param> /// <param name="commandText">The command text stored procedure or sql queries.</param> /// <returns></returns> public abstract DbDataReader Run(CommandType commandType, string commandText); /// <summary> /// Gets or sets the stored procedure parameters. /// </summary> /// <value> /// The stored procedure parameters. /// </value> public DbParameter[] Parameters { get; set; } /// <summary> /// Gets or sets the name of the stored procedure. /// </summary> /// <value> /// The name of the stored procedure. /// </value> public string StoredProcedureName { get; set; } /// <summary> /// Gets the default connection string from webconfig file. /// </summary> protected string ConnectionString { get { return ConfigurationManager.ConnectionStrings["SQLCONN"].ToString(); } } }
上面我們完成了抽象的提供者,這里僅僅是一些抽象的方法并沒有具體的實現,所以我們要通過具體的提供者來實現這些抽象的方法。接下來讓我們定義具體的提供者SqlDataProvider和OracleDataProvider。
圖 5具體提供者設計
/// <summary> /// The implementor of DataProvder. /// </summary> public class SqlDataProvider : DataProvider { #region Fields private string _connection = string.Empty; #endregion /// <summary> /// Default parameterless constructor used by Reflection. /// </summary> public SqlDataProvider() { // Just used by Reflection. } /// <summary> /// Initializes the provider. /// </summary> /// <param name="name">The name of provider, setting in webconfig file.</param> /// <param name="config">The value collection of config.</param> public override void Initialize(string name, NameValueCollection config) { // Due to ProviderBase has check name and config are available or not, // So no need validate name and config. base.Initialize(name, config); _connection = config["connectionString"]; if (string.IsNullOrEmpty(_connection)) throw new ConfigurationErrorsException("Connection string can't empty."); } /// <summary> /// Runs the specified transaction. /// </summary> /// <param name="transaction">The transaction.</param> public override void Run(DbTransaction transaction) { SqlHelper.ExecuteNonQuery(transaction as SqlTransaction, CommandType.StoredProcedure, StoredProcedureName, Parameters); } /// <summary> /// Runs the specified transaction. /// </summary> /// <param name="transaction">The transaction.</param> /// <param name="parameters">The parameters.</param> public override void Run(DbTransaction transaction, DbParameter[] parameters) { SqlHelper.ExecuteNonQuery(transaction as SqlTransaction, CommandType.StoredProcedure, StoredProcedureName, parameters as SqlParameter[]); } /// <summary> /// Runs the specified connection string. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="parameters">The parameters.</param> /// <returns></returns> public override DataSet Run(string connectionString, DbParameter[] parameters) { DataSet ds = SqlHelper.ExecuteDataset(connectionString, StoredProcedureName, parameters); return ds; } /// <summary> /// Runs the specified parameters. /// </summary> /// <param name="parameters">The stored procedure parameters.</param> /// <returns></returns> public override DataSet Run(DbParameter[] parameters) { DataSet ds = SqlHelper.ExecuteDataset(ConnectionString, StoredProcedureName, parameters); return ds; } /// <summary> /// Runs the scalar. /// </summary> /// <param name="connectionString">The connection string.</param> /// <param name="parameters">The parameters.</param> /// <returns></returns> public override object RunScalar(string connectionString, DbParameter[] parameters) { object obj = SqlHelper.ExecuteScalar(connectionString, StoredProcedureName, parameters); return obj; } /// <summary> /// Runs the scalar. /// </summary> /// <param name="transaction">The transaction.</param> /// <param name="parameters">The parameters.</param> /// <returns></returns> public override object RunScalar(SqlTransaction transaction, DbParameter[] parameters) { object obj = SqlHelper.ExecuteScalar(transaction, StoredProcedureName, parameters); return obj; } /// <summary> /// Runs the specified connectionstring. /// </summary> /// <param name="connectionstring">The connectionstring.</param> /// <returns></returns> public override DataSet Run(string connectionstring) { DataSet ds = SqlHelper.ExecuteDataset(connectionstring, CommandType.StoredProcedure, StoredProcedureName); return ds; } /// <summary> /// Runs this instance. /// </summary> public override void Run() { SqlHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, StoredProcedureName, Parameters); } /// <summary> /// Runs the specified parameters. /// </summary> /// <param name="commandType">Type of the command.</param> /// <param name="commandText">The command text stored procedure or sql queries.</param> /// <returns></returns> public override DbDataReader Run(CommandType commandType, string commandText) { SqlDataReader dr = SqlHelper.ExecuteReader(ConnectionString, CommandType.Text, commandText); return dr; } public SqlParameter[] Parameters { get; set; } }
我們很快就完成了SqlDataProvider類,這是由于我們把具體的實現都放在了SqlHelper中。在SqlHelper中包含具體的數據庫操作方法,在其中包含了一些煩瑣的數據庫操作方法,如果我們說要讓大家自己去完成這個Helper類,那么肯定是一個很痛苦的過程,而且嚴重影響了開發(fā)的效率,考慮到數據庫操作方法的可重用性微軟已經給我們提供了對SqlServer操作的Helper類(包含C#和VB)。
現在SqlDataProvider類基本完成了,接著我們創(chuàng)建一個DataProviderManager類,在它里面提供工廠方法用來創(chuàng)建提供者對象。
圖 6 DataProviderManager設計
在DataProviderManager類中的CreateProvider方法負責創(chuàng)建提供者對象(如:SqlDataProvider和OracleDataProvider)。
/// <summary> /// The factory method to creates the provider instance. /// </summary> /// <returns>The instances of provider.</returns> public static object CreateProvider() { LoadProviders(); return _provider; } /// <summary> /// Loads the providers by webconfig setting. /// </summary> private static void LoadProviders() { // providers are loaded just once if (null == _providers) { // Synchronize the process of loading the providers lock (SyncLock) { // Double confirm that the _provider is still null. if (null == _provider) { try { // Reads the webconfig file corresponding node. DataProviderSection section = (DataProviderSection) WebConfigurationManager.GetSection( "system.web/dataProviderService"); _providers = new DataProviderCollection(); // Creates provider instance, and invokes ProviderBase's Initialize function. ProvidersHelper.InstantiateProviders(section.Providers, Providers, typeof(DataProvider)); // Gets the default in the collection. _provider = Providers[section.DefaultProvider]; } catch { throw new ProviderException("Can't create instance"); } } } } }
通過上面的代碼我們hard code獲取提供者信息的配置文件節(jié)點,那么DataProviderManager類根據配置文件設置來實例化提供者對象,接下來讓我們設置配置文件。
圖 7 提供者模式配置文件設置
首先我們在configSections中,設置自定義數據庫提供者名稱(dataProviderService)和命名空間,接著我們在web節(jié)點中配置dataProviderService,在其中我們配置了默認的數據庫提供者為SqlDataProvider,接著我們再設置其它數據庫提供者OracleDataProvider。
現在我們已經完成了自定義數據庫提供者了,那么接下來讓我們通過一個簡單的web界面程序來測試一下吧!
圖 8 界面效果圖
由于時間的關系我們已經把界面設計好了,現在讓我們測試一下數據庫提供者。
圖 9 測試效果圖
/// <summary> /// Insert data into to database. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btnAdd_Click(object sender, EventArgs e) { try { if (!String.IsNullOrEmpty(this.txtUserName.Text.Trim())) { UserInfoParameters userInfo = new UserInfoParameters(this.txtUserName.Text.Trim()); DataProvider provider = DataProviderManager.CreateProvider() as DataProvider; provider.StoredProcedureName = "sp_AddUsertt"; provider.Run(userInfo.Parameters); this.lblMsg.Text = "Save successful."; } } catch (Exception ex) { this.lblMsg.Text = "Save failed."; throw ex; } }
上面我們調用SqlDataProvider把用戶名寫入到數據庫。
/// <summary> /// Get id from database. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void btnSelect_Click(object sender, EventArgs e) { try { if (!String.IsNullOrEmpty(this.txtUserName.Text.Trim())) { UserInfoParameters userInfo = new UserInfoParameters(this.txtUserName.Text); DataProvider provider = DataProviderManager.CreateProvider() as DataProvider; string strSql = String.Format( "SELECT UserID FROM UserInfo WHERE UserName = '{0}'", this.txtUserName.Text); SqlDataReader dr = provider.Run(CommandType.Text, strSql) as SqlDataReader; while (dr.Read()) { this.lblMsg.Text = "The user id is: " + dr[0].ToString(); } } } catch (Exception ex) { this.lblMsg.Text = "This id not existed"; throw ex; } } }
接著我們根據用戶名從數據庫中讀取用戶id。
/// <summary> /// Sets stored procedure parameters. /// </summary> public class UserInfoParameters { private string _userName; public UserInfoParameters(string userName) { _userName = userName; Build(); } private void Build() { SqlParameter[] sqlParameters = { new SqlParameter("@UserName", _userName) }; Parameters = sqlParameters; } public SqlParameter[] Parameters { get; set; } }
我們通過SqlDataProvider提供者成功把User Name寫入數據庫中,而且在數據庫中生成的id為11。
1.1.3 總結
通過本文的介紹相信大家對提供者模式(Provider)有了初步的了解,在我看來提供者模式不僅僅是包含策略模式(Strategy)思想,而且它通過工廠方法(Factory Method)更好地改善了繼承使得耦合度增加的缺點,所以說提供者包含了策略模式(Strategy)和工廠方法(Factory Method)設計思想。
我們知道所有提供者模式(Provider)必須繼承于抽象基類ProviderBase,然后我們就可以定義各種各樣的提供者。
希望通過閱讀本文后大家對于提供者模式有更深的了解。
提供者模式參考資料
Provider Model Design Pattern and Specification, Part 1
The ASP.NET 2.0 Provider Model.
Designing loosely coupled components in .NET Provider Pattern
|
|
關于作者:[作者]:
JK_Rush從事.NET開發(fā)和熱衷于開源高性能系統設計,通過博文交流和分享經驗,歡迎轉載,請保留原文地址,謝謝。 |

1.1.1 摘要 在日常系統設計中,我們也許聽說過提供者模式,甚至幾乎每天都在使用它,在.NET Framkework 2.0中微軟提出了提供者模式(Provider),所以我們可以發(fā)現...












浙公網安備 33010602011771號