Java代码设计风格建议
背景
我们设定讨论的场景为一个Web Project,它含有三层架构:负责接收http请求的controller层、负责进行业务逻辑实现的service层,负责从数据库获取数据的dao层。(暂不讨论其他层,比如和project外接口进行数据交换的sao层则不在讨论范围内)
对象类
8000行代码的service和1000行代码的service,大概率是后者更容易阅读,使用对象类就能辅助把8000行代码压缩到1000行。
对象类包括controller层用的DTO类、VO类,service层用的BO类,dao层使用的DO类。下面简单介绍这几个类的使用建议。
使用DO类作为从数据库提取数据的承接者(Data Object)
从数据库提取的数据可以用String,或map来承载。而这里建议使用do类进行数据承载,特别是只对一张表的数据进行提取时。
相较于使用map,DO对象可以提供数据自检能力。在DO类中可以提供方法让DO对象自己检查提取的数据是否符合业务要求,自己判断获取的数据组合属于的种类。
因为DO类是从数据库的持久化数据提中提取出来的数据,所以一般不建议在DO类中提供set方法,而是只提供get方法和各种数据组装和数据性质判断的方法。让其作为一个有自我检测能力和判断能力的对象存在,却不会随便插手数据库的事情,只做一个忠实的数据传输者。
因为不建议提供set方法,所以DO类的对象构造,建议通过各种有无参构造方法生成,或者使用格式化方法进行数据格式化。
使用DTO和VO类进行项目外部的数据传输者(Data Transfer Object,Value Object)
数据传输一个很方便的做法是通过map进行数据传输。这样做的好处是可以随便拓展和修改外部系统传输和接受的数据,坏处就是太自由了,不看传输者的代码无法知道map里面传输了什么。而且对传输数据的处理都要写在service层,导致service层原本的业务处理代码被数据封装代码冲乱,很难看懂。不用map的一个替代方案就是用DTO和VO这样的传输类进行数据的传输。一般我们定义DTO为从项目外接收数据的封装类,定义VO为从项目往外传送数据的数据类。
在DTO和VO中,只提供单位字段get的方法、构造方法的方法,一般不对单个字段提供set方法和二次格式化方法。如果有的方法需要单点更新类中的某个字段,建议驼峰使用format[字段名]来命令方法,提供类更新单字段的能力。对VO和DTO这种传输数据的对象类的定义是一个传输数据的承载者,使命是传送数据,解释数据,而不应有能力篡改数据。DTO类提取出来还有一个额外的好处,当调用方对数据的格式有变换时,只需要修改DTO的字段就能够应对变化,而不需要修改service层,影响到DAO层。
DTO还有个额外好处,能够把请求序列化,这样就能在内存中对相同请求进行识别,直接在对相同服务进行缓存承接大流量相同数据处理,返回DTO对应的VO。不过一般不建议使用这种缓存,建议使用原始数据的缓存。它们比较麻烦的一点是提前确定所有的字段,这需要和产品、前端做前期调查工作。不过这样做的好处是能够对自己的编码有更多的控制和了解,有利于将来的规范拓展。
使用BO类进行service内部数据传递(Business Object)
BO类的作用是在service层中做业务传输,在service层中起一个数据承载者的作用。对BO类,提供set和get,且可以根据业务需要提供各种方法能力。
Controller层
controller层在web的项目中主要负责接收外部的访问请求,这一层应该承担数据校验的责任,确保进来的数据都是合法的数据。后端应该由自己的能力去应对不合法的数据,比如不接受不合法数据,对不合法数据返回提示语等。
这一层的调用主要是service层提供的方法,不该直接调用dao层的方法。
Service层
service层是处理业务的主力层,在service层的方法中,代码应该专注在业务逻辑的实现,一些对象类的数据修改工作应该交给对象类来做而不是在service层中直接写各种set和get方法去给对象类赋值,这回极大的增大service层代码的阅读理解成本,把重要的业务逻辑淹没在数据组装的代码中。所以建议把数据组装的代码尽可能的融入到对象类中去,让对象类们自己把数据组装好,在service中直接使用。
如果发现service中不得不进行大量的与业务逻辑无关的逻辑,那么建议把这些逻辑整理成一个个独立的方法,然后在做一个工具类helper,用这个工具类把这些繁杂的逻辑收拾起来统一处理。当service中的业务逻辑遇上这些跳不开的非业务逻辑的时候,就调用helper去解决,然后把结果直接拉回service继续做业务逻辑。helper建议尽量写成static的形式。
service中的业务逻辑有时会有异常抛出,对于抛出的异常可以直接在service层抓掉打日志,也可以抛出上层(一般是controller层)去处理。特别提请:不要在catch里写业务逻辑,catch应该做catch的事情,掺杂业务逻辑会让其太重。
查询类service设计建议
- 先从DAO层提取出一条主干数据(把这条数据想象成一条鱼骨,上面带有些许的鱼肉)
- 然后再从DAO层提取剩余的数据(想象成剩余的鱼肉)
- 组装数据,把全部的数据整合起来(把鱼骨和鱼肉整合起来),成为一BO或者DTO,返回给调用方
DAO层
dao层就是从持久层(一般是数据库,比如oralce,mysql)提取数据使用的。这个层建议提取数据尽量提取独立的DO,独立于业务逻辑提取。如果是一些考虑到效能的编码,需要从多张表提取组合数据,可以用一个BO类去接住这样的数据。