代码结构中Dao、Service、Controller、Util、Model是什么意思,为什么划分?
简单点,不用长篇大论。你只要记住一个核心两个要点就可以了。
核心就是封装,也就是我入口小,里面大,你别管我用了几千几百行代码实现了什么功能,我一封装,就是一行API给你,你调我,我给你我说好的功能,这就是封装。
两个要点:1分层,2传递。 分层就是我把一组同样功能的封装放一起。和数据库打交道的放一起,这层就是DAO,database access object。 把提供业务功能的分一层,就是service,有的地方叫做domain service,把控场的分一层,controller。 传递就是我要在各个东西之间传递参数,是的函数有调用参数?这个是面向过程的思想传下来的,面向对象我们传递的是实体,对象,并不是你给我四个数,我帮你算个24点出来,而是你给我四张牌的结合,我帮你算个24点,这就是model,各种现实社会的实体,在这里抽象成model。
架构 就这么简单。
首发于知乎专栏。
适合受众:2年以下的初级程序员和0基础的门外汉
内容大纲:
为什么需要一个好的代码结构
什么样才是一个好的结构
每一个分类代表什么含义
是否适用于WEB,Android和IOS?
进一步的学习的话,是要学习系统架构么?
一、为什么需要一个好的代码结构
- 好的代码结构并不仅仅是为了看上去清晰,它更像是我们对一个系统的拆解和组装。
- 好的代码结构可以让你在遇到代码交接这种天理不容的情况时,减少提刀砍人的可能性。
- 好的代码结构可以让多人协作开发更容易,而不会缠缠绵绵到天涯,再相爱相杀。
我们经常形容一个坏的代码结构,像屎一样。
我们称它为一坨,说真的,接手过烂代码之后,真的找不到比屎更能描述自己感受的词了。
“屎”代表着混乱,一坨,各种杂质。接手一堆烂代码的难度就像是用一坨屎来做沙画。
有时候我们还会用一团毛线来形容代码,大概是这样的。
对的,这种感受是绝对不会错的。而我们要做的就是把这团毛线,变成像瑞士军刀一样的清晰。
你们觉得哪个更有成就感?
二、什么样才是一个好的结构
- 好的结构应该保持单一职责。
- 好的结构应该是通用的。
- 好的结构应该是有明确定义的。
这其实就是所谓的脚手架提供的最大的价值,一般而言,Java,Android,IOS都有一套明确的框架体系,JS本来没有,后来有了,然后。。他们就打起来了。
所以,好的体系应该就像。。。他们一样。
该喷火的喷火,该喷水的喷水,每个人分工都很明确。
三、每一个分类代表什么含义
1.Model
Model是模型,一般而言,会有人分的更细,VO,DTO等等。我并不推荐分的更细,这个Model常常和持久化的数据一一对应,如Mysql和MongoDB。
Model承载的作用就是数据的抽象,描述了一个数据的定义,Model的实例就是一组组的数据。整个系统都可以看成是数据的 流动,既然要流动,就一定是有流动的载体。
这个红圈标的就是Model。它就应该是一个纯数据的集合,就是被各种东西传来传去,被各种加工处理的数据团。
通常会有很多Model,一条业务流就是对应一条或者多条数据流,拿知乎为例子。
文章是一个Model,一般叫Article,包括Title,Summary,Author,Content等等。
评论也是一个Model,一般叫Comment,包括Content,userID等等。
对于初学者而言,第一个要学会,就是建模,把业务逻辑映射成数据模型。
2.Util
Util是工具的意思,一般来说,常常用来描述和业务逻辑没有关系的数据处理。
Util一般要和私有方法对比:私有方法一般来说是只是在特地场景下使用的,私有方法越多,代码结构越乱。常见的重构策略就是首先从一个很多行数的代码里抽象出若干个私有方法,然后再抽出公用的Util。
如果有可能,尽可能的少用私有方法,而是把他换成一个公用的Util,代表他和业务逻辑是不相关的。通常命名也是ArticleUtil,CommentUtil之类的。
像这种打包,不管是充气娃娃还是别的什么东西,都打包。你可以理解为图中的黑衣人就是一个Util。
某中程度上也会跟Service有点接近。但是Service一般而言,都是包含有业务逻辑的,很少能做单元测试。
Util一般来说,就是一个明确的输入和一个明确的输出结果。单元测试中,多数也是来测试Util。
积累好自己的Util是一件很重要的事儿。
3.Service
Service比Util的概念大很多,它的重点是在于提供一个服务。这个服务可能包括一系列的数据处理,也有可能会调用多个Util,或者是调用别的服务。总归一句话,就是,有什么事情,你来找我。
就像这个图上的妹妹一样,她就是一个Service,她能提供什么样的服务?这个是必须定义好的。如果是洗脚,她要帮你脱鞋,要端盆子烫你的脚。这里面,你的脚就是一个Model,盆子里的水相当于Util,不管里面放进去啥都能烫一烫。
帮你脱鞋可以是一个Service,也可以是一个私有函数,也可以是一个Util。看你的是让这个小妹妹帮你脱,还是别的小妹妹脱,还是自动脱鞋机。
如果是你自动脱。。。说明你在Model里面加上了功能,你的脚就不是一个纯粹的数据模型了,而是一个包含业务功能在里面的充血模型。
这样不好。老老实实让小妹妹帮你脱鞋不好么。
4.Dao
Dao一般而言,都是用来和底层数据库通信,负责对数据库的增删改查。
是的。他就是一个Dao。他从来不关心这些货物要去哪里,他只关心。入库,出库,查询和更换。
所谓的CRUD就是创建,读取,更新,删除。
Dao最好都是要独立出来。
到现在为止,最佳实践就是一个Service只对应一个Dao。Service会做一些额外的检查,如货物是否损坏,入库单是否完整,等等等等。
我并不推荐在Service里调用多个Dao,也不推荐在Service里调用多个Service,大多数情况下我都不推荐这么干。
具体原因以后再说,这也是一个开放性的话题。
现在我们分清楚了Model,Util,Service和Dao,可是谁来做总的调度呢?
5.Controller
控制中心,所有的指令,调度都从这里发出去。
哪一个Service做什么事儿,谁的数据提供给谁,一般而言,都是在Controller里实现的。
Controller也是最常见的容易产生脏代码地方,通常他们会把一些不该放到Controller里东西也放进来。
大概的感觉就是这样的。
干嘛的都有。想想如果打小针,抽血,查尿也混杂到门诊大厅的感觉?
可是大部分人写代码就是这样的。
四、是否适用于WEB,Android和IOS?
Java后台是有很清楚的结构的,毕竟在JSP里写Sql语句的蛮荒时代已经过去了。
Android本身就是一个良好的框架体系,基本上问题也不大,最多就是MVP和MVC的差别之类。
IOS虽然没有官方提供这种框架体系,特别是很多人喜欢直接在Dict里用key取数据,这本身就破坏了代码的层次性。
但是毕竟是有李明杰提供的Json解析Util,只是各家要求的力度而已。
最难以理解的是WEB,也就是JS。
我不是在黑JS,我是在黑JS程序员。分层结构一直都不是JS社区里最注重的,在JQuery时代更是如此,不管是Html还是JS还是CSS混在一起是正常的。
那个时候叫插件,现在改名了,叫组件。
你很难在JQuery里找到一套清晰的分层结构,就跟十几年前所有的人都在Jsp里写逻辑语句的道理差不多。
直到google的大神偶尔遛达过来一看,咦?你们怎么还在刀耕火种?我来给你们加点现代感的东西吧。
于是Angular横穿出世,一次性的构建了一个清晰的框架结构。每次看到Angular的时候都忍不住 惊叹,原来前端代码也可以这样!
而原来的感觉就是这样。。。
现在基本上可以分成两大阵营,一个是React和Vue,一个是Angular。
React和Vue本身更偏得于插件化,哦,不,组件化。所以他们需要便宜桶,来拼接整个前端的架构体系。
Angular却是有典型的Java架构风格,妥妥的硬汉子。
所以,实际上说,这套体系也是可以应用在WEB上的,就像Android和IOS一样的,但是你喜欢,或者不喜欢,自己选啦。
五、进一步的学习的话,是要学习系统架构么?
是的。进一步要学习,并不仅仅是学习系统架构。
这里还没有讲到Service的设计,互相之间的调用,解耦,服务之间的通信和管理。
消息队列这个神器还没有登场,MongoDB这种战略要塞也没出场。
所以以上内容,仅适用于2年以内的各种工程师。
程序是对现实的抽象和模拟。编程中的这些概念单纯看定义和名词解释是很难理解的,要在现实世界中找到对应关系。
以到银行转账为例:
- 选择查询、取款还是转账是controller
- 点钞验钞机是util
- 转账是service
- 账户是model
- 更新余额是DAO
所以如果现实中的业务结构是混乱的,即使开发中套用这些技术框架,也不可能写出结构清晰的程序。所以首先要业务建模和业务模式重组啊同学们!
先名词解释吧:
- DAO = Data Access Object = 数据存取对象
- Service = 服务
- Controller = 控制器
- Util = 工具
- Model = 模型
首先,一个代码是不是有完善的结构,和是不是有上面这些东西没有什么关系,只是通常来说,我们做一个大项目会把项目分解成很多不不同的模块(Module),然后根据用途和角色,我们对这些模块有一个通用的命名规则,这也就是上面这些英文单词的来历。
所以,请一定记住,项目中是否包含这些模块或者单词,和你的项目结构是否完善一毛钱关系没有。但是当你的项目结构相对完善的时候,你会发现有这样一些角色的存在。
接下来一个个的来详细讨论一下这个东西是如何出现的:
DAO,数据存取对象。通常我们会遇到很多要和数据库打交道的场景,如果为每一个场景都去写一些SQL语句,会让我们代码变得很奇怪,我们希望我们的代码比较干净整洁,那么一个很容易想到的方案就是把数据库封装一下,让我们和数据库的交道看起来比较像和一个对象打交道。这个对象通常就是DAO,当我们操作这个对象的时候,这个对象会自动的产生SQL语句来和数据库打交道,而我们只需要和DAO打交道就可以了。
当然,从本质上来说,DAO并不需要和数据库有什么必然的联系,DAO只是数据存取对象的缩写,所以只要是把数据持久化包装成一个对象的访问(读写),这种对象都可以被称之为DAO,譬如,用JSON格式存到硬盘上。
Service,我们有时候会需要一些相对独立,与业务系统没啥关系的功能。但不是所有的功能都可以做成一个服务,服务是一个相对独立的功能模块,完成一些指定的工作,这些工作高度抽象和通用。一个典型的服务像是数据库服务、缓存服务、文件存储服务、身份验证服务、消息队列服务等。
关系型数据库服务可以视为是一个接收SQL语句并给出一个查询结果的服务,我们不必关心服务内部具体是如何处理问题的,我们只需要关注服务给出的接口。
并不是所有的模块都适合做成服务,一个服务首先最重要的是独立性,这个服务必须可以独立的完成指定的工作。复杂的服务可能依赖于一个或者多个更基础的服务,但是服务通常不应当依赖于任何具体的业务代码,服务必须具有高度的抽象性。关系型数据库服务就具有高度的抽象性,事实上只要我们撰写标准的SQL,不论后面是MySQL、SQL Server还是Oracle,他们都会呈现出几乎完全相同的行为。
一个更为简单的服务像是缓存服务,我们把一坨数据放进去,在一段时间内可以快速的获取这坨数据,在一段时间后数据就会消失。
当你的代码需要一个高度抽象高度标准化的功能,而这个功能又不能简单的实现,或者这个功能需要很多资源的配合,例如缓存服务需要内存资源,而数据库服务通常需要磁盘资源,身份验证服务通常需要数据库服务支持。这个时候就可以考虑将这个功能模块做成一个服务。
服务作为基础的部件,我们通常会要求它能够应付各种各样的情况,一个优质的服务通常会有非常高的可用性,因为我们的系统可能会依赖于各种各样的服务,而整个系统的可用性将不可能比其中任何一个服务的可用性更高。
所以服务的特征:抽象、独立、稳定。
评论中提到Java项目中的Service通常是指Business Service,这里也简单说说。
很多时候,我们发现服务的特征对于我们开发一个大型项目的时候很有帮助。就拿独立性来说,关系型数据库服务如SQL Server可以独立发售,独立安装和部署。它可以自行测试自己的接口,如果都达到了预期的效果,并且能够应付各种情况,这个服务就可以作为一个产品独立的出售给我们安装。这意味着关系型数据库服务并不需要配合我们的业务系统一起进行测试和调试,或者作出什么变更。
在完成一个大型的业务系统时,我们发现一些子模块或者子系统也可以像服务一样独立的部署和测试。例如会员系统、支付系统、订单系统等等,他们的业务逻辑可能非常复杂,但是逻辑相对独立,并且高度内聚。如果我们将这些系统分别独立的测试和部署,就可以大大的降低我们的测试、部署和运维的成本。
这些可以独自完成某一方面业务功能,高度内聚,可以独立部署测试的模块,我们可以称之为Business Service,业务服务。它同样具有服务的特征,抽象、独立和稳定。一个会员系统内部的逻辑可能非常复杂(积分规则,分级规则,风险控制,行为数据),但是在其外部,会员的概念可以非常简单。
Util,Util通常来说是我们找不到合适的名字的时候的选择,Util就是工具,在做项目的时候我们总会遇到一些奇奇怪怪的小功能或者重复的代码需要提取。像是URL编码或者解码(当然这个类库通常会提供,不过就以 .NET Framework 为例,提供这个方法的类型名称叫做HttpUtility),或是自创的加密签名算法等等。
Model,模型,通常来讲,我们会把模型和另一个东西放在一起来说:View,视图。
模型通常认为是视图的内核,何谓之视图?我们正在与之交互的知乎网站的界面就是视图,而模型是指他的内核:数据。
知乎的数据是问题和答案,问题分为标题和描述,答案有内容和作者以及各种状态。知乎有很多个UI,例如移动页面,普通PC页面,手机APP,以及改版前的旧界面,这些被称作不同的视图。而所有这些形态迥异的视图,其内核都是一样的,这个内核我们就称之为模型(Model)。
将Model和View的概念拆分开来,有助于我们关注不同的方面,也可以更有效的分工。有些工程师更关注于内核也就是模型,通常来说,他们被称之为后端工程师。有些工程师更关注于用户界面的交互和展示,通常来说,他们被称之为前端工程师。
个人简单理解:
DAO(Data Access Object)顾名思义就是用 OO 的方式去执行数据库的操作,包括函数化的 CRUD,可以自己去写也可以用 ORM 框架。
Service 可以处理事务和业务逻辑,比如用户登录的校验等。
Controller 其实就与业务无关了,它更多的是将 Service 层的结果加以处理返回给 View,也可能会处理一些简单的参数检验工作。
Util 的话就是一些辅助类(或函数),涉及面可能比较广,粒度小,比如生成 MD5 的便利函数等。
Model 是最简单的,Java 里对应的就是 Beans,ORM 框架也将根据 Model 中的定义去生成 SQL 语句,在其他语言中也是一些 Plain Old Object,自身可能会有一些数据表达的处理功能,但更多的不应该涉及副作用。