从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,就干的这个事咯。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
2011-11-11 asp.net mvc 小心Html.RenderAction