最近跟一些朋友讨论关于工厂模式的一些东西,觉得很有必要写篇东西把抽象工厂的应用方法写清楚。GoF在抽象工厂一章中没有花很多篇幅在如何应用抽象工厂上。所幸ADO.NET为我们提供了一个非常标准的抽象工厂的例子,同时也是非常实用的一个接口——DbProviderFactory
我希望借一个利用DbProviderFactory重构网站的例子来回答两个关于抽象工厂的常见问题:
首先我们来看看这个重构之前的网站,这是我花1个小时写的一个很具有代表性的例子,一个数据库表,增删改查四个页面,查询页面用了控件我们可以不用管它,为了演示,增删改我没有使用FormView。类似这样的网站随处可见,当然这并不是一个好的例子,没有异常处理,也没有任何封装,只是一个供大家理解的例子而已。 这是一个依赖于SqlSever Client的网站,我们无法把它部署在其它类型的数据库上。现在让我们通过重构让它支持多种数据库。 使用抽象工厂之前,首先要让代码符合OO的基本原则之一,依赖倒转原则。所以我们把所有的命名空间System.Data.SqlClient全都换成System.Data.Common,相应的类也换成以Db开头的接口。然而不幸的是,当我们换完之后,会发现有语法错误: |
这些讨厌的new,我们没有办法创建一个接口的实例,所以new就没有办法不依赖具体的类。这样,我们就不得不将原来的命名空间加回去了,而且也无法做到针对多种数据库编程。到这里,你大概已经可以猜到解决方案跟工厂有关了。这就是我们使用工厂的原因:构造函数不能多态。
接下来我们回到比较具体的问题,既然是用工厂重构可以解决问题,如何做呢?当然是优先使用.net类库提供的抽象工厂类:DbProviderFactory。既然有了类,然后我们面临的具体问题就是:何时创建工厂?保存工厂的变量放在什么地方? 让我们来考虑一下工厂的在所用范围:这是一个很简单Web应用,我们只需要一个数据库,所以决定我们使用数据库类型的工厂应该是全局的,在web应用程序启动时就创建。那么这个位置显然应该是Global类的静态属性。 这里要顺带提一下Global类如何创建,右键单击我们的网站root,然后add new Item 如图中所示选择Global Application class就可以了。 |
之后我们会得到Global.asax文件,其中包含Global类。之后我们在Global类中加入DbProvider,并且在Application_Start中创建它的具体类型。 |
有了DbProvider之后,我们可以把原有的代码重构为依赖DbProvider创建数据库相关对象的形式了,这里仍然帖Update的例子: |
这个时候,Update.aspx.cs不再需要包含System.Data.SqlClient等具体数据库类型相关的命名空间了,成为了彻底不依赖具体数据库类型的类。 用同样的方法重构delete.aspx.cs和insert.aspx.cs之后,我们的整个系统就是与数据库解耦的系统了,只需要在Global.asax中一处创建正确的工厂,并且在配置文件中写好连接字符串,就可以工作在任何数据库中。 |