购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.3 解耦第二步——工厂模式解决new的问题

刘朋:接口层就长这个样子啊,好像也没什么用嘛。

鹏辉:如果没有接口层的话,我们是这样定义的:

CustomerDAL customerDAL=new CustomerDAL();

有了接口层以后,我们的定义是这样的:

IDAL customerDAL=new CUstomerDAL();

在写法上的差别并不是太大,而且增加了接口层还会增加开发量。接口层的意义也就是在设计层面吧。

冲冲:你讲的接口层的目的是阻断BLL和DAL层之间的关联,但我们在写代码的时候还是会去new一个DAL对象,好像也没有达到目的啊。

MOL:你们提的问题,也是我们接下来要探讨的知识。其实,冲冲的疑问是比较关键的,只要把new的问题解决了,大家也就自然了解了接口层其实并不是鸡肋。

为了不使用new来实例化一个DAL,那么我们会考虑使用工厂模式来达到这个效果。

工厂模式算是一种比较常见的设计模式(Design Pattern),工厂模式的目的是不用new来创建一个实例。接下来用“演绎法”来描述一下工厂模式。

前面已经说过,加入接口层以后,实例化一个对象可能是这样:

IDAL customerDAL=new CUstomerDAL();

我们不想用new的方式来创建实例,而希望是这样:

IDAL customerDAL=工厂.创建(实例类型);

第一反应,就是把new的操作放到工厂中去执行。那么工厂就需要根据传入不同的实例类型来“制造”出不同的对象实例。最简单的方式是使用switch…case来输出对应的对象实例。例如:

public static object GetInstences(string className) 
{ 
    switch (className) 
    { 
        case "userDal": 
            return new userDal(); 
        case "orderDal": 
            return new orderDal(); 
        case "pageDal": 
            return new pageDal(); 
        case "customerDal": 
            return new customerDal(); 
        default: 
            return null; 
    } 
} 

这样就不用再显式地去new一个对象出来了,这种写法就是传说中的“简单工厂”,因为它足够简单,所以会带来很多问题。

首先,这种写法是违反DRY(Do not Repeater Yourself)的,我们可以看到大量重复的代码(return new…)。

其次,这种写法需要穷举项目中所有需要new的类名。这是一个不小的工作量,而且很有可能会遗漏掉某些类。如果直接使用简单工厂来代替new其实并没有用,反而会带来额外的工作。

那么就没有其他的办法了吗?

在.NET的机制里,有一种技术叫反射。反射可以动态地加载DLL,并实例化类(class)。例如:

//只有在当前解决方案里添加了该dll的引用后才可以使用Load   
Assembly objDALAss = Assembly.LoadFrom(@"E:\Project\Elands.JinCard.DAL. dll"); 
  
//Elands.JinCard.DAL.userDAL类的全路径   
Type t = objAss.GetType("Elands.JinCard.DAL.userDAL"); 
//动态生成类StringUtil的实例   
IuserDal obj = System.Activator.CreateInstance(t) as IuserDal; 

上面的代码就是反射的一个基本应用,使用反射的基本步骤如下。

(1)找到dll所在的路径。

(2)找到class/interface的全路径。

(3)实例化class/interface。

这意味着不需要再去判断输入的类型,也就不需要出现大量重复的代码,最后完成的工厂代码如下:

public class DALSimpleFactory 
{ 
    public static Object GetInstences(string assemblyName, string typeName) 
    { 
        return Assembly.Load(assemblyName).CreateInstance(typeName); 
    } 
} 

是不是看起来干净许多了?

接下来再来创建一个userDAL,用来说明这个工厂代码如何使用:

IuserDAL userDal=DALSimpleFactory.GetInstences("Elands.JinCard.DAL", "Elands. JinCard.DAL." + "userDal") as IuserDAL; 

OK,到这里为止工厂模式的使用就已经讲述完了。

鹏辉:用工厂模式的写法确实是没有new了,但是也不可避免地要写入硬编码,比如要实例化userDAL的时候,必须写入userDAL的路径,并且写入userDAL的全路径,这样和直接new有什么本质的区别呢?

MOL:new属于静态编译,也就是在编译网站的时候userDAL就会被编译放到网站中。而反射生成userDAL属于动态编译,生成网站的时候不会被编译到网站中,只有在使用userDAL的时候才会生成。最重要的是,工厂模式有效地切断了BLL和DAL层的强关联。那么实例化userDAL的时候,我们需要传入userDAL所在的DLL的路径、userDAL的全路径,这样看似有点“剪不断,理还乱”的关系,其实不然。我们传入的参数是字符串(string)类型的。也就是说,传入Elands.JinCard.DAL是正确的,传入“阿猫阿狗”也未尝不可。重点是“字符串”,作为字符串,传入的参数就可以写在配置文件中,当需要新增或修改的时候,直接修改配置文件就可以了,而不用重新编译项目。

例如,配置文件是这样的:

<appSettings> 
  <add key="userDalRef" value="Elands.JinCard.DAL,Elands.JinCard.DAL.userDal"/> 
  <add key="customerDalRef" value="Elands.JinCard.DAL,Elands.JinCard.DAL. customerDal"/> 
  <add key="orderDalRef" value="Elands.JinCard.DAL,Elands.JinCard.DAL. orderDal"/> 
</appSettings> 

这样,就把每一个DAL的配置放在了web.config中。每一个add节点都是一个DAL配置,其中,key值表示DAL配置名称,value表示配置值。key和value中描述的配置名称一定要一目了然,比如上面的配置中,key="userDalRef"表示userDal这个DAL的引用(Refrence);value中是一个以逗号分隔的字符串,其中,逗号前面的部分是DAL所在的DLL的路径,逗号后面的部分是DAL类的全路径。例如,要实例化一个userDAL,那么实例化的代码就是:

public void GetuserDal() 
{ 
    string[] dalCfgArr = System.Configuration.ConfigurationManager.AppSettings ["userDalRef "].Split(','); 
    IuserDAL userDal=GetInstences(dalCfgArr[0],dalCfgArr[1]) as IuserDAL; 
    //调用IuserDAL的方法 
    userDal.Select(); 
} 

这样就完美解决了鹏辉所提到的硬编码的问题。

刘朋:这样做确实是达到了“解耦”的目的,但调用工厂去创建一个实体,也就是说,怎么老感觉有点没有“解”干净的意思呢?BLL虽然不依赖于DAL了,但却依赖了工厂。

MOL:对,BLL从依赖具体的DAL,变成了依赖抽象的工厂,这是工厂模式给我们带来的最大好处。但是DIP(Dependence Inversion Principle,依赖倒置原则)告诉我们,高层模块不应该依赖低层模块,两者都应该依赖于抽象。显然,BLL依赖了工厂,而工厂又依赖了DAL,这样互相依赖,造成了解耦不彻底。那么如何彻底地解耦呢?

PS:顺便提一下,new一个class的时候,.NET会先在内存堆中开辟一块用来存放实例的空间,然后再对实例进行初始化。而开辟内在堆空间的时候,这些空间并不是连续的,所以当一个项目中大量地使用了new的时候,就会造成内存堆中存在大量的碎片。这种现象会对真实的系统造成故障,而且这种故障会给排查过程带来很大的困难。

例如,一个服务器的内存是1GB,我们把一个网站发布到这台服务器上,这个网站在运行一段时间后,服务器的空间内存可能只剩余200MB,这200MB由一些小于2KB的内存碎片组成。如果这个时候再去new一个大于2KB的对象,那么程序就会报错。 fn6PO6Sbd/OsDCMme8TVqZQO4Da+EkLJnnsGXk9izdLUWJbMTQ9Oe82aYHSYoOXM

点击中间区域
呼出菜单
上一章
目录
下一章
×