.NET2.0 框架中的 AbstractFactory 模式

如果我们要做一个需要能够支持各种数据库的ORM。可以用到AbstractFactorySingleton等设计模式。

我们先分析一下,要实现一个ORM,我们首先需要一个能够和各种不同数据库平台交互的一致的接口,我们把它叫做DBWR,意思就是“数据库读写”。它可以读取数据库中的数据,对数据库执行DDL操作。我们认为它有这些功能:

1、读取数据,以DataTable的形式返回,其原型是:

public DataTable GetData(string Sql);

2、执行Sql语句,并返回受影响的行数,其原型是:

    public int ExecSql(string Sql);

 在实践中,我们经常以参数的形式作为某种条件来读取数据或者把数据写入数据库,这样一是方便,二是安全。因此还应该加上如下函数:

3public DataTable GetData(string Sql, IDataParameter[] Params);

4public int ExecSql(string Sql, IDataParameter[] Params);

另外,我们经常只要从数据库中获取一个值,完全没有必要用DataTable的形式,就用一个object好了。所以我们再加上下面的函数:

5public object GetSingleValue(string Sql);

6public object GetSingleValue(string Sql, IDataParameter [] Params);
    下面是DBWR的简单类图:
  
    

在上面的函数定义中,我们为什么用IDataParameter,而不用SqlParameter呢?请注意,我们的DBWR类是需要与各种支持.net的数据库平台进行交互的,如果用SqlParameter的话,就只能与Sql Server进行交互了。

.net中,对数据库进行连接的通用接口是IDbConnection.执行Sql语句的是IDbCommand,当然还有用来填充IDbCommandParameters属性IDataParameter,当然,如果我们需要更加方面的操作,那就需要一个IDbDataAdapter的实例。但是各数据平台对他们的实现是不同的,不要期望Sql Server能够与Oracle能够统一。我们的DBWR类需要获取这一系列操作数据库的、相关的接口的实例,但是又不知道这些实例的具体的类,怎么办呢?这个时候,就可以用AbstractFactory设计模式来解决这个问题。它的意图就是“提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类”。

我们把这个能够为我们提供这些能与不同数据库平台打交道的接口,但又不必指定具体类的工具叫做DbInterfaceGenerator(意思就是“数据库接口生成器”)它有这些功能:

1、获取能够连接数据库的IDbConnection的实例:

public abstract IDbConnection GetConnection();

2、获取能够执行Sql语句的IDbCommand的实例:

public abstract IDbCommand GetCommand();

3、获取IDataParameter的实例:

   A、按参数名称,参数的值获取参数:

public abstract IDataParameter GetParameter(string ParameterName, object ParameterValue);

B、按参数名称,参数的长度,参数的精度,参数的数据库类型的名字,比如“varchar”获取参数:

        public abstract IDataParameter GetParameter(string ParameterName, int? Length, byte? Scale, string DbTypeName);

C、按参数名称,参数的长度,参数的精度,参数的数据库类型的名字,比如“varchar”,参数的值获取参数:

   public abstract IDataParameter GetParameter(string ParameterName, int? Length, byte? Scale, string DbTypeName, object ParameterValue);

   为什么把参数的长度,参数的精度声明为int?呢?因为有时我们并不需要指定参数的长度和精度,这时,就用null代替好了。

4、获取IDbDataAdapter

public abstract IDbDataAdapter GetAdapter();


    下面是的
DbInterfaceGenerator简单类图:
  

那么对应Sql Server数据库,那就是SqlDbInterface,它返回的就是分别是SqlConnectionSqlCommandSqlParameterSqlDataAdapter对应Oracel数据库,那就是OracleDbInterface.它返回的就是OracleConnectionOracleCommandOracleParameterOracleDataAdapter

对其他的数据库,就是XXXDbInterface返回就是对应的Connneciton,Command,ParamterDataAdapter

下面是它们的简单类图:

 不同数据库进行交互的问题解决了,我们又碰到了一个问题,我们的DBWR类每次需要用到DbInterfaceGenerator类某个子类的实例的时候,难道每次都new一个新的实例么?这样做,既不方便,也会影响性能。一般来说,只要我们决定了用哪种数据库,系统一旦启动,就不会再更改。所以我们用以与数据交互的DbInterface只要有一个实例就可以了,它在第一次需要的的时候就生成。可以用Singleton模式达到我们的要求。它的目的就是“保证一个类仅有一个实例,并提供一个访问它的全局访问点”。

我们给DbInterfaceGenerator添加一个能够让其他类可以访问它本身唯一实例的“全局访问点”,叫做DbInterface

改进后的DbInterfaceGenerator 的类图如下:
  
   

对于DBWR类,我们也需要提供一个这样的访问点,叫做Instance。改进后的类图是:
 

我们非常幸运,因为我们使用C#,实现这种Singleton模式,非常容易。两条简单的语句就可以实现DBWRInstance

public static readonly DBWR Instance = new DBWR();

protected DBWR() { }

当然,我们也可以把我们的DBWR类的构造函数申明为private.

不过,对于DbInterfaceGeneratorDbInterface,就没有这么容易了。但也不要紧张,大家注意到public static readonly DBWR的构造函数没有?套用领导们常说的话:“只要抓住了public static readonlyprotected/private 构造函数(){} 这两个基本点,我们的属性就符合Singleton模式了”。其实现如下:

    public static readonly DbInterfaceGenerator DbInterface = GetDbInterface();

    private static DbInterfaceGenerator GetDbInterface()

    {

       string InterfaceType = System.Configuration.ConfigurationManager.AppSettings["DbInterfaceType"];

       string InterfaceAssembly = System.Configuration.ConfigurationManager.AppSettings["DbInterfaceAssembly"];

       if (!string.IsNullOrEmpty(InterfaceType) && !string.IsNullOrEmpty(InterfaceAssembly))

       {

          return (DbInterfaceGenerator)
      System.AppDomain.CurrentDomain.CreateInstance(InterfaceAssembly, InterfaceType).Unwrap();

       }

       return new SqlDbInterface();

    }

        GetDbInterface函数中,我们用到了“反射”,这是.net中一个非常重要的特性,他对于实现工厂方法,提供了“无与伦比的方便”。相关函数还有“Activator.CreateInstance”等。

        如果我们的数据库平台不是Sql Server,那么需要在我们的App.config文件的AppSettings 节中,添加“DbInterfaceType”和“DbInterfaceAssembly”这两个元素。

具体代码因为较长,不便在这里贴出,请参考DongLiORM中的DBWR.cs, DbInterface.cs,SqlDbInterface.cs



.NET2.0 框架中的 AbstractFactory 模式


由于最近有了宝宝,导致夜里写文章的时间越来越短,而白天又忙于开发。没办法,只有挤时间去写东西了。
前些天在园子里看到了这篇文章,http://www.cnblogs.com/Yahong111/archive/2007/07/18/822946.html,对里面写的内容浏览了一下,这里首先对作者的实践精神表示赞赏。我这里只是从别的角度阐述一下AbstractFactory
在这种应用场景下的发展,内容不多,希望大家见谅。

1. DbService 是我看到的第一个使用抽象工厂模式设计的数据库链接类。其实在几年前网上就有人写了这个
东西,代码不多,但应用的背景和设计思路与永红(http://www.cnblogs.com/Yahong111/archive/2007/07/18/822946.html)
的思路非常相似。大家可以去网上搜一搜就知道了。

2. 微软也采用了这种模式在他们的NET2.0框架中。我甚至可以对NET2.0中的新添的一些类与模式文章中类内
容对号入座。只要大家Reflector看一下.NET2.0框架代码即可。为了清楚起见,我只在这里做一下简单说明,旨在
抛砖引玉。

System.Data.Common.DbProviderFactory, 位置在System.Data.dll中。注意这是一个 抽象类

public abstract class DbProviderFactory
{
// Methods
protected DbProviderFactory();
public virtual DbCommand CreateCommand();
public virtual DbCommandBuilder CreateCommandBuilder();
public virtual DbConnection CreateConnection();
public virtual DbConnectionStringBuilder CreateConnectionStringBuilder();
public virtual DbDataAdapter CreateDataAdapter();
public virtual DbDataSourceEnumerator CreateDataSourceEnumerator();
public virtual DbParameter CreateParameter();
public virtual CodeAccessPermission CreatePermission(PermissionState state);

// Properties
public virtual bool CanCreateDataSourceEnumerator { get; }
}

这个类在Abstract Factory模式下就是AbstractFactory,也就是永红所说的那个DbInterfaceGenerator接口。但
为什么微软要用抽象类而不是接口(本身也偏向于使用接口),也只有微软才知道了:)。
目前我的理解是减少基类中一些不必要的实现代码或进行条件判断时(如为 null),有相关的返回值进行比较
如下:
public virtual DbCommand CreateCommand()
{
return null;
}



这个函数就是DbProviderFactory类的一个虚方法,这个方法会在子类中将被重写, 而子类(SqlClientFactory)
中的代码类内容如下(位于System.Data.SqlClient.SqlClientFactory)

public sealed class SqlClientFactory : DbProviderFactory
{
// Fields
public static readonly SqlClientFactory Instance;

// Methods
static SqlClientFactory();
private SqlClientFactory();
public override DbCommand CreateCommand()
{
return new SqlCommand();
}

public override DbCommandBuilder CreateCommandBuilder()...
public override DbConnection CreateConnection()
{
return new SqlConnection();
}

public override DbConnectionStringBuilder CreateConnectionStringBuilder()...
public override DbDataAdapter CreateDataAdapter();
public override DbDataSourceEnumerator CreateDataSourceEnumerator()...

public override DbParameter CreateParameter()
{
return new SqlParameter();
}



public override CodeAccessPermission CreatePermission(PermissionState state);

// Properties
public override bool CanCreateDataSourceEnumerator { get; }
}

看到这里大家应该明白一些了吧。顾名思议,SqlClientFactory就是AbstractFactory模式图中的
ContreateFactory1类 ,对应永红的图中的SqlDbInterface接口。另外大家也可以从ORACLE,MySql官方
下载最新的DLL,反射里面的OracleClientFactory和MySqlClientFactory代码,大家会看到DLL中的相关方
法“模样”大同小异。

注:为了使用这个DbProviderFactory类,微软还出了一些辅助类,如:
DbParameter 继承自 IDataParameter
DataAdapter 继承自 IDataAdapter
DbDataAdapter 继承自 IDbDataAdapter
DbDataReader 继承自 IDataReader

并且这些类基本都是抽象类:)

说到这里,可能有人要说了,微软这么做到底是图什么呀!原有的接口在那呆的挺好,大家只要继承并实现里面想要的功能部份就行了。其实这里恰恰巧说明了微软的“聪明”之外,不知道大家看到没有,在
前些年JAVA社区还在讨论框架,AOP , IOC 时,Microsoft官方似乎反应比较“冷淡”,但如今天我们从.net
2 以及 .net3看到了对于AOP和很多精典框架模式的嵌入,这里有从JAVA类库思想中得来的经验,也有开源
社区的“贡献”。微软恰恰在上述的那些理论或代码框架成功(甚至有成功案例)情况下快速加入这些东西,
这不能不说微软很“聪明”。 不好,又跑题了:)

3.因为最近公司的产品在向.net2 框架过度,所以就出现了这么一个问题:如果产品要在这两个框架都
能顺利运行,同时还要体现.net2 的一些“新”的优势,那只有写两个"不同"的链接类。其中一个用到了上面
的那个DbProviderFactory类,而在.net1 下基本采用接口,参考DbService,同时为了保证两套代码的逻辑对
应关系,我简单的实现了SqlClientFactory等在2.0框架中才有的类。实现的方法参见.net2的相关类代码(本
人采用同名方式引入了这几个类)。另外关于使用实现我们也采取“反射”方式。这与永红所想的方法是“
不谋而合”了 :)

说到这里,有一个现象我想再谈谈我的看法,以前看设计模式只有从开源的框架,技术社区或别人的源码
中分析。其实当我们还在为找出精典的应用设计模式的代码(net版本)而苦苦搜索时,微软已经给我们打开了一个新的“窗口”,那就是.net 框架本身。这个与我们朝夕相伴的家伙其实就是一个非常好的学习设计模式的例子。虽然.net 框架推陈出新的速度让人只能用"惊叹"来形容,但模式这种东西在里面却安然无恙,必定这是框架的骨头呀。所以我希望大家多挖.net框架的代码,这会使我们的程序设计水平有长足的提高。

posted @ 2007-07-24 12:45  peak  阅读(321)  评论(1)    收藏  举报