掌握应用分层:高内聚低耦合的艺术
文章目录
- 应用分层
- 软件设计原则:高内聚低耦合
- 项目示例
- 应用分层的好处
- 企业规范
应用分层
应用分层是一种软件开发设计思想, 它将应用程序分成N个层次, 这N个层次分别负责各自的功能, 多个层次之间协同提供完整的功能
常见的MVC设计模式, 就是应用分层的一种具体体现
为什么需要应用分层?
一开始,为了让项目快速上线,我们通常是不考虑分层的。但是随着业务越来越复杂,大量的代码混在一起,会出现逻辑不清晰、各模块相互依赖、代码扩展性差、牵一发而动全身等问题。
如何分层
MVC 就是把系统分成了 Model(模型), View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是一种标准的软件分层架构
目前主流的开发方式是 “前后端分离” 的方式, 后端开发工程师不再需要关注前端的实现, 所以对于Java后端开发者, 又有了一种新的分层架构: 把整体架构分为表现层、业务逻辑层和数据层。这种分层方式也称为"三层架构"
- 表现层: 是最靠近用户的一层,负责接收页面的请求,给页面响应数据
- 业务逻辑层: 负责处理业务逻辑
- 数据层: 负责存储和管理与应用程序相关的数据,负责业务数据的维护操作,包括增、删、改、查等操作
这三个部分, 在Spring的实现中, 均有体现:
- Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
- Service:业务逻辑层。处理具体的业务逻辑。
- Dao:数据访问层,也称为持久层。负责数据访问操作,包括数据的增、删、改、查
MVC 和 三层架构 的区别和联系
从概念上来讲, 二者都是软件工程领域中的架构模式.
- MVC架构模式由三部分组成, 分别是: 模型(Model), 视图(View)和控制器(Controller)
- 三层架构将业务应用划分为:表现层, 业务逻辑层, 数据访问层
MVC中, 视图和控制器合起来对应三层架构中的表现层,模型对应三层架构中的业务逻辑层, 数据层以及实体类
二者其实是从不同角度对软件工程进行了抽象
- MVC模式强调数据和视图分离, 将数据展示和数据处理分开, 通过控制器对两者进行组合
- 三层架构强调不同维度数据处理的高内聚和低耦合, 将交互界面, 业务处理和数据库操作的逻辑分开
角度不同也就谈不上互相替代了,在日常的开发中可以经常看到两种共存的情况,但是二者的目的是相同的, 都是"解耦,分层,代码复用"
软件设计原则:高内聚低耦合
- 高内聚:实现某个功能的时候,如果和这个功能相关的代码是集中放在一起的,就认为是"高内聚"; 如果是散落在项目的各个文件,各个角落中就认为是"低内聚"。高内聚就非常方便找到并修改代码
- 低耦合:软件中各个层、模块之间的依赖关联越小越好。修改一处代码, 其他模块的代码改动越少越好
内聚和耦合并没有必然的联系
- 内聚描述的是模块内部的事情(也有可能是模块之间)
- 耦合描述的是模块之间的事情
高内聚低耦合矛盾吗?
不矛盾, 高内聚指的是一个模块中各个元素之间的联系紧密程度, 低耦合指的是各个模块之间的紧密程度
这就好比一个企业, 包含很多部门, 各个部门之间的关联关系要尽可能的小, 一个部门发生问题, 要尽可能对降低对其他部门的影响, 这就是"低耦合"; 但是部门内部员工关系要尽量紧密,遇到问题一起解决克服,这叫做"高内聚"。又比如邻里邻居, 楼上漏水, 楼下遭殃, 就是"高耦合";一个家庭内部的关系越紧密越好,一个家庭成员生病, 其他成员帮忙照顾, 就叫"高内聚",一个家庭尽可能减少对另一个家庭的影响,就是"低耦合"
项目示例
- controller包下是控制层: 接收前端发送的请求,对请求进行处理,并响应数据
import com.example.messagewall.model.MessageInfo;
import com.example.messagewall.service.MessageInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RequestMapping("/message")
@RestController
public class MessageController {@Autowiredprivate MessageInfoService messageInfoService;@RequestMapping(value = "/publish", method = RequestMethod.POST)public Boolean publish(@RequestBody MessageInfo messageInfo) {//添加到数据库messageInfoService.insert(messageInfo);return true;}@RequestMapping("/getList")public List<MessageInfo> getList() {//从数据库查return messageInfoService.query();}
}
- service包下是业务逻辑层: 处理具体的业务逻辑
import com.example.messagewall.mapper.MessageInfoMapper;
import com.example.messagewall.model.MessageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class MessageInfoService {@Autowiredprivate MessageInfoMapper messageInfoMapper;public Integer insert(MessageInfo messageInfo) {return messageInfoMapper.insert(messageInfo);}public List<MessageInfo> query() {return messageInfoMapper.query();}
}
- mapper包下是数据访问层: 负责数据访问操作,包括数据的增、删、改、查
import com.example.messagewall.model.MessageInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;@Mapper
public interface MessageInfoMapper {//添加留言@Insert("insert into message_info (`from`,`to`, `message`) values " +"(#{from}, #{to}, #{message})")Integer insert(MessageInfo messageInfo);//查询留言@Select("select * from message_info where delete_flag=0")List<MessageInfo> query();
}
- model包下是实体类通常对应数据库中的表,包含该事物的属性(数据)和行为(方法),是数据存储和业务逻辑的基础载体
import lombok.Data;@Data
public class MessageInfo {private Integer id;private String from;private String to;private String message;private Integer deleteFlag;private String createTime;private String updateTime;
}
前端messagewall.html
代码:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>留言板</title><style>.container {width: 350px;height: 300px;margin: 0 auto;/* border: 1px black solid; */text-align: center;}.grey {color: grey;}.container .row {width: 350px;height: 40px;display: flex;justify-content: space-between;align-items: center;}.container .row input {width: 260px;height: 30px;}#submit {width: 350px;height: 40px;background-color: orange;color: white;border: none;margin: 10px;border-radius: 5px;font-size: 20px;}</style>
</head><body><div class="container"><h1>留言板</h1><p class="grey">输入后点击提交, 会将信息显示下方空白处</p><div class="row"><span>谁:</span> <input type="text" name="" id="from"></div><div class="row"><span>对谁:</span> <input type="text" name="" id="to"></div><div class="row"><span>说什么:</span> <input type="text" name="" id="say"></div><input type="button" value="提交" id="submit" onclick="submit()"><!-- <div>A 对 B 说: hello</div> --></div><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script><script>$.ajax({type: "get",url: "/message/getList",success: function(messages) {if(messages!=null) {for(let message of messages) {var divE = "<div>"+message.from +"对" + message.to + "说:" + message.message +"</div>";$(".container").append(divE);}}}});function submit(){//1. 获取留言的内容var from = $('#from').val();var to = $('#to').val();var say = $('#say').val();if (from== '' || to == '' || say == '') {return;}//调用后端接口,发表留言$.ajax({type: "post",url: "/message/publish",contentType: "application/json",data: JSON.stringify({"from": from,"to": to,"message": say}),success: function(result) {if(result) {//成功//2. 构造节点var divE = "<div>"+from +"对" + to + "说:" + say+"</div>";//3. 把节点添加到页面上 $(".container").append(divE);//4. 清空输入框的值$('#from').val("");$('#to').val("");$('#say').val(""); } else {//失败alert("发布失败");}}});}</script>
</body></html>
配置文件application.yml
:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: '123456'driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # 配置打印 MyBatis日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #配置驼峰自动转换
pom文件引入依赖部分:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency></dependencies>
按我配置的话,MySQL建库建表语句是这样的:
CREATE DATABASE IF NOT EXISTS mybatis_test;USE mybatis_test;CREATE TABLE message_info (id INT AUTO_INCREMENT PRIMARY KEY COMMENT '消息ID',`from` VARCHAR(255) COMMENT '发送者',`to` VARCHAR(255) COMMENT '接收者',message VARCHAR(255) COMMENT '消息内容',delete_flag TINYINT DEFAULT 0 COMMENT '逻辑删除标志:0=有效,1=无效',create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
);
全部搞定之后访问http://127.0.0.1:8080/messagewall.html
就能交互啦!
应用分层的好处
- 降低层与层之间的依赖, 结构更加明确, 利于各层逻辑的复用
- 开发人员可以只关注整个结构中的其中某一层, 极大地降低了维护成本和维护时间
- 可以很容易的用新的实现来替换原有层次的实现
- 有利于标准化
企业规范
适用于多数企业, 均不做强制要求. 具体以所在企业为准
- 类名使用大驼峰风格,但以下情形例外:DO/BO/DTO/VO/AO
- 方法名、参数名、成员变量、局部变量统一使用小驼峰风格
- 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词
常见命名风格介绍
- 大驼峰: 所有单词首字母都需要大写, 又叫帕斯卡命名法, 比如: UserController
- 小驼峰:除了第一个单词,其他单词首字母大写,比如: userController
- 蛇形:用下划线 (_) 作为单词间的分隔符,一般小写,又叫下划线命名法,比如: user_controller
- 串形:用短横线 (-) 作为单词间的分隔符,又叫脊柱命名法,比如: user-controller