分层解耦—代码的耦合内聚

一、三层架构

Java的三层架构是一种常见的软件架构设计模式,用于将应用程序的不同功能和责任分配到不同的层级,以提高可维护性、可扩展性和可重用性。
它可以包括以下三个层级:
1.表现层:也称为用户界面层,负责与用户进行交互,并将用户的请求发送到其他层进行处理。
2.业务逻辑层:也称为服务层或控制层,包含应用程序的核心业务逻辑。它接收来自表现层的请求,处理数据逻辑、业务规则和算法,并协调多个数据访问的操作。在Java中,可以使用POJO(Plain Old Java Object)或Spring框架的服务层来实现业务逻辑层。
3.数据访问层:也称为持久层或数据层,负责处理数据的持久化和访问。它与底层数据库进行交互,执行数据的读取、写入和更新操作。在Java中,可以使用JDBC(Java Database Connectivity)或ORM(Object-Relational Mapping)框架(如Hibernate)来实现数据访问层。

使用三层架构的优点
明确的职责划分、降低模块之间的耦合度、提高代码的可重用性和可测试性。此外,它还为不同层级的开发人员提供了分工合作的便利,并促进了代码的维护和扩展。

下面我们来进一步了解一下三层架构在代码中的样子

  • 数据访问 : 负责业务数据的维护操作,包括增、删、改、查操作
  • 逻辑处理 :负责业务逻辑代码的处理
  • 请求处理、响应数据:负责,接收页面的请求,给页面响应

上述大致可以分为三部分

  • Controller:接收前端发送的请求,对请求进行处理,并响应数据
  • Service:处理具体的业务逻辑
  • Dao:负责数据的访问操作,包含数据的增删改查

大致过程为:
浏览器发送请求,控制端(Controller)接收请求处理,并做出响应发送给服务端(Service),服务端进行数据的逻辑处理后发送给数据层(Dao),数据层在数据库中查找数据后返回服务层,服务层接收数据后发送给控制层,控制层再返回给浏览器,供客户端阅读。

各层代码如下:

//Controller@RestControllerpublic class UserController {private UserServiceA userService = new UserServiceA();@RequestMapping("/listUser")public Result listUser() throws Exception {// 调用service, 获取数据List<User> userList = userService.listUser();// 响应数据return Result.success(userList);}}

@RestController:标记类注解,结合了@Controller 和 @ResponseBody注解的功能

@Controller:用于标记一个类,表示这个类是一个控制器,用于处理用户的请求和响应
@ResponseBody:用于标记一个方法,表示该方法返回的结果将被直接写入HTTP响应体中,而不是被解析为视图名称
@RestController:使用 @RestController 注解标记的类,可以简化返回 JSON 或 XML 数据的操作,因为它会自动将方法返回的对象转换为相应的格式,并写入响应体中。这样,我们就不需要在每个方法上再添加 @ResponseBody 注解了。

//Servicepublic class UserServiceA {private UserDaoA userDao = new UserDaoA();public List<User> listUser() throws Exception {//调用 dao 层, 查询数据List<User> userList = userDao.listUser();//2. 对数据进行逻辑处理 - 对地址 address 中的 province 属性后, 添加上 "省/市"for (User user : userList) {Address address = user.getAddress();address.setProvince(address.getProvince()+" 省/市");address.setCity(address.getCity()+" 市/区");user.setAddress(address);}return userList;}}
//Daopublic class UserDaoA {public List<User> listUser() throws Exception {//1. 从文件中查询数据String file = UserController.class.getClassLoader().getResource("user.xml").getFile();List<User> userList = XmlParserUtils.parse(file);System.out.println(userList);return userList;}}

二、认识耦合

1.软件设计的原则是: 高内聚低耦合
2.内聚即为: 软件中各个模块的内部功能联系
高内聚的理解: 模块元素中的各个元素之间联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高
3.耦合即为 : 各个软件之间的依赖、关联程度
低耦合的理解:软件中各个层、模块之间的依赖关系程度越低越好
4.高内聚低耦合的目的:使程序模块的可重用性、移植性大大增强

耦合的解释
当我们在开发时进行加载XML数据,当我们在使用三层架构对原始的代码进行了编写时,controller调用service,service调用dao。在controller中调用service,我们需要在controller中new一个service对象,那么此时controller的代码就耦合了service。
在service中调用dao,我们就需要在service中new一个到对象,那么此时service的代码就耦合了dao。
代码如下


怎么解耦呢?
解耦就需要定义一个统一的接口(多态的体现)

具体代码的实现

//UserService接口public interface UserService {public List<User> listUser();}
//UserServiceImplpublic class UserServiceImpl implements UserService {private UserDao userDao = new UserDaoA();public List<User> listUser(){//调用 dao 层, 查询数据List<User> userList = userDao.listUser();//2. 对数据进行逻辑处理for (User user : userList) {Address address = user.getAddress();address.setProvince(address.getProvince()+" 省/市A");address.setCity(address.getCity()+" 区/县A");user.setAddress(address);}return userList;}}

这里调用dao层的作用是什么呢?
为了更好的实现解耦
当我们将创建的实例对象,都存储在一个容器中,如果需要这个对象,就不用再去单独new一个对象了,直接在运行时通过容器提供就可以了。
这里就涉及到了两个过程:
(1)控制反转 (IOC):将对象交给容器管理的过程
(2)依赖注入(DI):应用程序运行时,容器为其提供运行时所需的资源
(3)bean:IOC容器中创建、管理的对象

  • Service层 及 Dao层的实现类,交给IOC容器管理
    在类上加上 @Componet 注解,就是将该类声明为IOC容器中的bean
@Componentpublic class UserServiceA implements UserService {@Autowiredprivate UserDao userDao ;public List<User> listUser(){//调用 dao 层, 查询数据List<User> userList = userDao.listUser();//2. 对数据进行逻辑处理for (User user : userList) {Address address = user.getAddress();address.setProvince(address.getProvince()+" 省/市A");address.setCity(address.getCity()+" 区/县A");user.setAddress(address);}return userList;}}

在成员变量上加上 @Autowird 注解,表示在程序运行时,Springboot会自动的从IOC容器中找到UserService类型的bean对象,然后赋值给该变量

代替了查找数据的代码:

@Componentpublic class UserDaoA implements UserDao {public List<User> listUser() {//1. 从文件中查询数据String file = UserController.class.getClassLoader().getResource("user.xml").getFile();List<User> userList = XmlParserUtils.parse(file);System.out.println(userList);return userList;}}

@Component 的注解的衍生

可以使用value对其进行赋值

依赖注入
使用 @Autowird ,默认是按照类型进行,如果存在 多个相同类型的bean,就会报错
有以下解决方法
1) @Primary 注解

2)@Qualifier 注解
通过@Autowired,配合@Qualifier 来指定我们当前要注入哪一个bean对象。在@Qualifier的value属性中,指定注入的bean的名称

3)通过@Resource注解,并指定其name属性,通过name指定要注入的bean的名称。这种方式呢,是按照bean的名称进行注入。

@Autowird与@Resource的区别

  • @Autowird 属于spring框架,默认按照bean的类型注入。 可以配合 @Qualifier注解,实现按照名称注入。
  • @Resource是JavaEE自带的注解,根据bean的名称进行注入的。