从3层开始

最近面试很多人,每个人来,扯啊扯的都会扯到3层上,不论是有工作经验的还是没工作经验的,既然你要面试技术,3层总应该做些准备,以防被人问住。

不过答案真是,超过一半以上的人,张口就说model。

为了不必要的误会,我一般还会再细问一下,你的model是不是那种只有一堆属性,没有方法的?100%的都说是。

最后招了几个刚毕业的小孩,在公司做培训。所以记录下讲的东西,以供之后再培训可以省点事

从最最简单原始的3层

为了说明问题,我写一些模拟代码,代码只为说明问题,不是真实的东西,不必纠结是否有什么实际意义

   1: public class View
   2: {
   3:     public void Search()
   4:     {
   5:         int age = 20;
   6:         string name = "张三";
   7:         Bll bll = new Bll();
   8:         var users=bll.Search(age,name);
   9:     }
  10: }
  11:  
  12: public class Bll
  13: {
  14:     public IEnumerable<int> Search(int age,string name)
  15:     {
  16:         SqlDal dal = new SqlDal();
  17:         //dal.Search(...);
  18:         return null;
  19:     }
  20: }
  21: public class SqlDal
  22: {
  23:     public IEnumerable<int> Search(/*....*/)
  24:     {
  25:         //....查库
  26:     }
  27: }

用一个简单的例子大概说明一下。

首先,3层是 表现层,业务逻辑层,数据访问层。从上到下,上层知道相邻的下层,下层不知道上层,上层也不知道下层的再下层。

也就是说,表现层知道业务层,业务层知道数据访问层,表现层不知道数据访问层,反着,数据层不知道业务层,业务层不知道表现层。

啥叫不知道呢,就是不感知,不关心。我干我的事,你爱咋地咋地。

那数据访问层的下层是谁呢?就是数据库,说的小点就是sql,mysql之类的,说的大点就是存储介质(硬盘)

按照上面的分层,业务逻辑层,是不应该知道存储介质的。

这里插点其他的话。这3层里,核心是业务,因为对你的客户而言,有价值的是你的业务。

表现,也并不仅仅局限于一种形式。同样的业务,我可以做b/s,做c/s,甚至做手机端,这都是表现层的东西,下面应该是统一的业务层来处理不同的表现层发来的数据。

所以,3层和mvc是两码事,至少和asp.net mvc是两码事,你不用asp.net mvc还可以用web form,你不用b/s,还可以用win form,你不用.net,还可以用java,php,等等。业务逻辑可以以服务的方式对外提供,而被表现层使用

对上上面的示例代码,有两方面的问题。

第一,对业务层来说,怎么就那么确定你要new的是SqlDal了?如果由业务层来负责确定new SqlDal,那事实上,业务层已经知道存储介质了。这违背了分层的意义

所以,业务层,不能排脑袋就指定了数据访问用SqlDal,他只是知道,有数据访问这么个东西,具体是啥,不知道。

所以这里应该是面向接口的

   1: public class SqlDal:IDal
   2: {
   3:     public void Search(/*....*/)
   4:     {
   5:         //....查库
   6:     }
   7: }
   8:  
   9: public interface IDal
  10: {
  11: }

首先,我们建个接口,然后让SqlDal实现接口

关键在业务里怎么办?

如果仅仅是把SqlDal dal=new SqlDal()改成IDal dal=new SqlDal()这一点意义也没有,还是你确定了new出来的。

可以引入Factory来解决

   1: public class Bll
   2: {
   3:     public IEnumerable<int> Search(int age,string name)
   4:     {
   5:         var dal = Factory.GetDal();
   6:         //dal.Search(...);
   7:         return null;
   8:     }
   9: }
  10:  
  11: public class Factory
  12: {
  13:     public static IDal GetDal()
  14:     {
  15:         return new SqlDal();
  16:     }
  17: }

此时,业务并不知道到IDal是谁,那是由Factory高速我的。

我们还可以让别人的人来高速,谁呢?谁用我Bll,谁告诉我,我该用那个IDal

   1: public class Bll
   2: {
   3:     private IDal dal;
   4:  
   5:     public Bll(IDal dal)
   6:     {
   7:         this.dal = dal;
   8:     }
   9:  
  10:     public IEnumerable<int> Search(int age,string name)
  11:     {
  12:         //dal.Search(...);
  13:         return null;
  14:     }
  15: }

这就改成了依赖注入的方式,我们通过构造函数注入这样的手段,来让使用bll的人,告诉bll改使用怎样的IDal。

这时,bll是完全不知道存储介质的。我们可以用一个容器来解决注入的问题。

第一个问题,相对比较好理解,加个注入容器就能很好的解决

第二个问题:

最终我们总是要去查库的,在使用原生sql的情况下,请问sql语句,或者更窄一点说,where语句,写在那里?

第一种答案是写在业务层,那大概的代码会是这样

   1: //bll
   2: public IEnumerable<int> Search(int age,string name)
   3: {
   4:     string where = "where name='" + name + "' and age=" + age;
   5:     dal.Search(where);
   6:     return null;
   7: }
   8: //dal
   9: public IEnumerable<int> Search(string where)
  10: {
  11:     //....查库
  12:     var sql = "select * from tab " + where;
  13:     //...
  14: }

这样带来的问题是,业务层要想拼接sql,就意味着,首先,你知道他一定是存在关系型数据库里的,其次你还知道表结构。这又是知道了与存储介质有关的东西了。

但是好处是,数据访问层,只需要接收where,不需要关系上层是谁,上层发给我什么where,我就用什么where查

第二种答案是写在数据访问层,那参数怎么传呢?一般的代码大概会是这样

   1: //bll
   2: public IEnumerable<int> Search(int age,string name)
   3: {
   4:     dal.SearchByNameAndAge(age,name);
   5:     return null;
   6: }
   7: //dal
   8: public IEnumerable<int> SearchByNameAndAge(int age, string name)
   9: {
  10: //....查库
  11: var sql = "select * from tab where ...";
  12: //...
  13:  
  14: }

注意,我这里给dal的方法重新命名了一下,之所以要重新命名是因为,如果按照这样的传参形式,就意味着,对不同的业务逻辑的方法,会调用不同的数据访问层的方法。我每加一个业务逻辑的方法,可能都有一个与之对应的数据访问层上的方法(简单情况考虑)。

这意味着,数据访问层,其实是知道业务逻辑了。但好处是,业务逻辑层不用知道具体的存储介质。

那怎么才能在两种答案中,各取他们好的地方,而不取他们坏的地方呢?

这个时候,我们需要一种通用的数据结构,来表达各式各样的查询条件,在业务逻辑层去构建好这个查询条件,传递到数据访问层,而由数据访问层来解析这个数据结构中的数据,把它翻译成真正的sql语句(或者其他)

首先,我们象征性的建立一个表达where的数据结构

   1: public class WhereClass
   2: {
   3: }

然后来看两层之间的数据传递

   1: //bll
   2: public IEnumerable<int> Search(int age,string name)
   3: {
   4:     var where = new WhereClass();
   5:     //用age参数和namge参数,给where赋值
   6:     dal.Search(where);
   7:     return null;
   8: }
   9: //dal
  10: public IEnumerable<int> Search(WhereClass where)
  11: {
  12: //....查库
  13:     var wheresql = "...";//解析whereclass生成
  14:     var sql = "select * from tab "+wheresql;
  15: //...
  16:  
  17: }

到目前为止,业务逻辑层是不知道存储介质的,而数据访问层,只要写一个通用的search方法,就可以了,他也不用知道具体有什么业务逻辑。

可是,开发难度,其实是增加了;我们要定义一个通用的数据结构来表达where,还要为这个数据结构写一个sql翻译器,说不定还得写什么mysql翻译器,oracle翻译器等等。想想就很苦逼

不过不管怎么样,先大概想想吧。

一个where语句,各种条件的and,or,然后还有括号,括号里面,又可以and or,又可以括号,哎呀,就是个树啦。

其实微软已经帮我们做了这样一个通用的结构了,而且大部分的数据库也做了这种结构的翻译器。

就是linq啦,这种结构就是Expression<>

到这个时候,能够发现,数据访问层,其实已经不能算一层了,他更像是一个通用的数据访问组件,他可能并没有很多的类,很多的方法。

数据访问组件,对内把expression翻译成具体存储介质可以识别的东西去执行,对外以类的形式体现以供调用。

那些orm,就干的这个事咯。

原文地址:https://www.cnblogs.com/czcz1024/p/4089474.html