企业级应用架构(一) 三层架构之解耦
前言
前段时间朋友拿了个网站给我,让我帮忙添加几个小功能,我爽快的答应了,但是当我打开源码,我瞬间就奔溃了,整个项目连最基本的三层框架也没有搭建,仅仅是封装了一个sqlhelp作为数据库的操作接口,项目中的SQL查询语句无处不在,业务逻辑紧紧耦合在UI逻辑中,看到这样的代码,坦白来说,我什么兴致都没有了,但是碍着人情,我硬着头皮,把基本功能的完成交差,通过这件事情,我对软件分层进行了深入的思考。
三层架构
说到三层架构,大伙都很熟悉,我也不再多啰嗦了,我们直接快速搭建一个。
项目的引用关系是:StructWed->BLL,Model;BLL->DAL,Model;DAL->Model。下面我们来演示一下程序流程,假设我们的数据库有一张订单表Order表,我们的业务是针对Order表进行的增删改查,那么根据三层架构的编程模式,我们就需要建立起对应的Model层实体,数据访问层实体和业务层实体,我们分别用OrderModel,OrderDAL,OrderBLL表示,代码如下,由于仅仅是为了演示,所以我并未提供相应的实现。
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Mode { public class OrderModel { public int ID { get; set; } public int productName { get; set; } public DateTime CreateTime { get; set; } } }
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Mode; namespace DaL { public class OrderDAL { /// <summary> /// 向Order表插入数据 /// </summary> /// <returns>成功:true,失败:false</returns> public bool Insert() { return true; } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { return true; } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { return true; } } }
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DaL; using Mode; namespace Bll { public class OrderBLL { protected OrderDAL orderDal = new OrderDAL(); public bool Insert(OrderModel model) { //业务点1 //业务点2 return orderDal.Insert(); } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { //业务点1 //业务点2 return orderDal.Update(model); } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { //业务点1 //业务点2 return orderDal.Delete(id); } } }
View Code
好了,现在让我们把目光聚焦到OrderBLL上面来。我们发现OrderBLL对数据库Order表的所有操作都是在调用其内部创建的orderDAL实体的同名方法,换句话来说OrderBLL指示其内部orderDAL实体去完成数据库的增删改查。
好现在,我们思考一下。假设有一天我们发现我们数据访问层的orderDAL实体代码写的很不优雅,效率极差,我们想用一个更优雅的实体,比如OrderActiveDAL去替换掉它,那么这个时候我们OrderBLL的代码就必须做相应的改动,如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DaL; using Mode; namespace Bll { public class OrderBLL { protected OrderActive orderActive = new OrderActive(); public bool Insert(OrderModel model) { //业务点1 //业务点2 return orderActive.Insert(); } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { //业务点1 //业务点2 return orderActive.Update(model); } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { //业务点1 //业务点2 return orderActive.Delete(id); } } }
View Code
这显然不是一种好的处理方式。我们追求的是在替换orderDal实体的同时,不修改OrderBLL的任何代码,换句话说就是解除BLL层与DAL层的耦合,为了达到这个目的,我们添加一个叫IDAL的接口类库,如图
特别注意,我们在BLL层中添加了对IDA的引用,同时移除了对DAL的引用,这就意味着我们在BLL中无法创建(new)DAL层中的任何实体。下面我们在IDA项目中定义IOrder接口,如下
using Mode; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace IDal { public interface IOrder { /// <summary> /// 向Order表插入数据 /// </summary> /// <returns>成功:true,失败:false</returns> bool Insert(); /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> bool Update(OrderModel model); /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> bool Delete(int id); } }
View Code
我们让DAL引用IDAL,同时让OrderActiveDAL,OrderDal分别实现IOrder接口,如下
using IDal; using Mode; using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DaL { class OrderActiveDAL : IOrder { /// <summary> /// 向Order表插入数据 /// </summary> /// <returns>成功:true,失败:false</returns> public bool Insert() { return true; } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { return true; } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { return true; } } }
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Mode; using IDal; namespace DaL { public class OrderDAL : IOrder { /// <summary> /// 向Order表插入数据 /// </summary> /// <returns>成功:true,失败:false</returns> public bool Insert() { return true; } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { return true; } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { return true; } } }
View Code
现在我们队BLL层OrderBLL做相应的改动,把数据访问实体orderDAL的类型,定义为接口IOrder类型,如下
using System; using System.Collections.Generic; using System.Linq; using System.Text; using DaL; using Mode; using IDal; namespace Bll { public class OrderBLL { protected IOrder orderDal; public OrderBLL() { //在这里需要实力化orderDal } public bool Insert(OrderModel model) { //业务点1 //业务点2 return orderDal.Insert(); } /// <summary> /// 修改Order表数据 /// </summary> /// <param name="model">表数据对应实体</param> /// <returns>成功:true,失败:false</returns> public bool Update(OrderModel model) { //业务点1 //业务点2 return orderDal.Update(model); } /// <summary> /// 删除Order表指定ID的记录 /// </summary> /// <param name="id">表ID</param> /// <returns>成功:true,失败:false</returns> public bool Delete(int id) { //业务点1 //业务点2 return orderDal.Delete(id); } } }
View Code
好了,现在让我们把目光聚集OrderBLL的构造函数。我们知道,OrderBLL会调用orderDAL去操作数据库,而orderDAL的类型为IOrder,也就是说但凡实现了IOrder接口的类实例都可以赋值给orderDAL。但是现在问题的关键是我们如何为orderDAL赋值,前面我们说过BLL移除了DAL的引用,所以在BLL层中直接去new数据访问层的实例,是不可能的。这里我提供两种处理方式,第一种采用IOC容器如spring.net帮助我们创建DAL层实例然后在OrderBLL的构造函数中赋值给orderDAL,另一种则是利用工厂模式和反射来创建DAL层实例了,本文将详述第二种,至于第一种在后面的系列中会有专门的章节讲述。现在我们在项目中添加一个Factoy工厂类库,并让BLL层引用Factoy类库,如图:
接着我们来写工厂类。我们从配置文件中读出程序集路径和类的全名,利用反射的原理创建DAL层的实例,代码如下
using IDal; using System; using System.Collections.Generic; using System.Configuration; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Factory { public class OrderDALFactory { private static readonly string AssemblyName = ConfigurationManager.AppSettings["Assembly"]; private static readonly string className = ConfigurationManager.AppSettings["className"]; public static IOrder CreateOrder() { return (IOrder)Assembly.Load(AssemblyName).CreateInstance(className); } } }
View Code
最后我们在OrderBLL的构造函数利用OrderDALFactory工厂给orderDAL赋值
public OrderBLL() { //在这里需要实力化orderDal orderDal=OrderDALFactory.CreateOrder(); }
这样我们就实现了BLL与DAL层的解耦,当我们BLL需要不同的IOrder实体的时候,我们只需要修改相应的配置文件即可。
总结
程序框架间层与层之间的解耦是富含挑战的一项工作,充分的体现出了面向对象的编程思想,巧妙的运用了各类设计模式。本文实现方法相对简单,仅当抛砖引玉。在接下来的文章中,我将就三层架构中各个层次的抽象与封装做详细说明。因为各个层次的抽象与封装是针对不同技术点来实现的,比如数据访问层,对EF技术与ADO.net技术的抽象与封装细节上就会有所不通。但总体思想是一致的,那就是我们必须为每个层次抽象出统一的接口,供上层引用,同时我们必须提供相应的注入方式,为调用层引用的接口实例赋值实例化。