1.1.1 摘要
在日常系统设计中,我们也许听说过提供者模式,甚至几乎每天都在使用它,在.NET Framkework 2.0中微软提出了提供者模式(Provider),所以我们可以发现.NET Framkework中有很多类命名都含有“Provider”,例如:IFormatProvider,RSACryptoServiceProvider等等,由此可见它们都间接或直接使用了提供者模式这一设计思想,现在让我们来介绍一下提供者模式(Provider)。
1.1.2 正文
首先让我们通过提供者模式(Provider)结构图,了解什么是提供者模式(Provider)。
图1提供者模式(Provider)结构图
通过上面的结构图我们发现提供者模式(Provider),并没有想象中的那么复杂而且整个结构就是使用了一些继承关系而已。
提供者模式(Provider)的结构图,和我们熟悉的策略模式(Strategy)结构基本一致就是通过继承扩展不同种类的算法。
策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
图2策略模式(Strategy)结构图
所以很多人都喜欢把提供者模式(Provider)和策略模式(Strategy)进行对比,更甚至有些人认为就是策略模式(Strategy),其实不然我们可以发现每种提供者它们都是继承于一个基类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; } }
通过上面的代码我们发现,ProviderBase只包含一些描述字段和属性,还有就是一个无参构造函数和一个初始化方法Initialize(string name, NameValueCollection config)。该方法是提供者模式实现动态调用的核心方法(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"); } }
其实这个方法功能很简单就是去读我们配置文件相应节点的值, 现在我们知道可以通过重新 Initialize 方法,可以实现从配置文件中读取 Provider 的信息(如:name,type,connectionString 等)。我们都知道继承使得类之间的耦合度增加, 这就是策略模式的一个缺点具体算法通过继承来进行扩展。但我们看到提供者模式(Provider)使用了一种比较灵活方法对具体提供者进行扩展。
图 3 ASP.NET中的提供者模式(Provider)
通过上图我们发现.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类,那么肯定是一个很痛苦的过程,而且严重影响了开发的效率,考虑到数据库操作方法的可重用性微软已经给我们提供了对SqlServer操作的Helper类(包含C#和VB)。
现在SqlDataProvider类基本完成了,接着我们创建一个DataProviderManager类,在它里面提供工厂方法用来创建提供者对象。
图 6 DataProviderManager设计
在DataProviderManager类中的CreateProvider方法负责创建提供者对象(如: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获取提供者信息的配置文件节点,那么DataProviderManager类根据配置文件设置来实例化提供者对象,接下来让我们设置配置文件。
图 7 提供者模式配置文件设置
首先我们在configSections中,设置自定义数据库提供者名称(dataProviderService)和命名空间,接着我们在web节点中配置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