后端三层架构的分层与解耦

1.引用

上一篇的请求和响应案例中,我们会发现案例中:解析XML数据,获取数据的代码,处理数据的逻辑的代码,给页面响应的代码全部都堆积在一起了,全部都写在controller方法中了。
在这里插入图片描述
整个工程代码的复用性比较差,而且代码难以维护,那么就需要分层开发。

2.分层

2.1 三层结构介绍

一个类或一个方法,就只做一件事情,只管一块功能,这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利用后期的维护。

  • 请求处理、响应数据(controller):负责,接收页面的请求,给页面响应数据。
  • 逻辑处理(service):负责业务逻辑处理的代码。
  • 数据访问(dao):负责业务数据的维护操作,包括增、删、改、查等操作。
    在这里插入图片描述
  • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。
  • Service:业务逻辑层。处理具体的业务逻辑。
  • Dao:数据访问层(Data Access Object),也称为持久层。负责数据访问操作,包括数据的增、删、改、查。

2.2 基于三层架构的程序执行流程

在这里插入图片描述

  • 前端发起的请求,由Controller层接收(Controller响应数据给前端)
  • Controller层调用Service层来进行逻辑处理(Service层处理完后,把处理结果返回给Controller层)
  • Serivce层调用Dao层(逻辑处理过程中需要用到的一些数据要从Dao层获取)
  • Dao层操作文件中的数据(Dao拿到的数据会返回给Service层)

2.3 代码拆分

2.3.1控制层controller

接收前端发送的请求,对请求进行处理,并响应数据

@RestController
public class EmpController {
    //面向接口EmpService创建EmpService类型的业务层对象
    private EmpService empService = new EmpServiceA();

    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service层对象, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }
}

2.3.2业务逻辑层service

处理具体的业务逻辑

  • 业务接口
//业务逻辑接口(制定业务标准)
public interface EmpService {
    //获取员工列表
    public List<Emp> listEmp();
}
  • 业务实现类
//业务逻辑实现类(按照业务标准实现)
public class EmpServiceA implements EmpService {
    //面向接口EmpDao创建EmpDao类型的dao层对象
    private EmpDao empDao = new EmpDaoA();

    @Override
    public List<Emp> listEmp() {
        //1. 调用dao对象, 获取数据
        List<Emp> empList = empDao.listEmp();

        //2. 对数据进行转换处理 - gender, job
        //empList集合的数据来自dao层,因此需要在service中调用dao
        
        empList.stream().forEach(emp -> {  
            //处理 gender 1: 男, 2: 女
            String gender = emp.getGender();
            if("1".equals(gender)){
                emp.setGender("男");
            }else if("2".equals(gender)){
                emp.setGender("女");
            }

            //处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
            String job = emp.getJob();
            if("1".equals(job)){
                emp.setJob("讲师");
            }else if("2".equals(job)){
                emp.setJob("班主任");
            }else if("3".equals(job)){
                emp.setJob("就业指导");
            }
        });
        return empList;
    }
}

2.3.3数据访问层dao

负责数据的访问操作,包含数据的增、删、改、查

  • 数据访问接口,访问数据库、接口、文件等,实现的方式有很多,因此面向接口编程,使用统一的接口,更改实现类即可,增强代码灵活性。
//数据访问层接口(制定标准)
public interface EmpDao {
    //获取员工列表数据
    public List<Emp> listEmp();
}
  • 数据访问实现类
//数据访问实现类
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

在这里插入图片描述

3.解耦

3.1 内聚与耦合的介绍

  • 内聚:软件中各个功能模块内部的功能联系。

  • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

软件设计原则:高内聚低耦合,目的是使程序模块的可重用性、移植性大大增强。。

高内聚指的是:一个模块中各个元素之间的联系的紧密程度,如果各个元素(语句、程序段)之间的联系程度越高,则内聚性越高,即 “高内聚”。

程序中高内聚的体现:EmpServiceA类中只编写了和员工相关的逻辑处理代码
在这里插入图片描述

低耦合指的是:软件中各个层、模块之间的依赖关联程序越低越好。

程序中耦合代码的体现:把业务类变为EmpServiceB时,需要修改controller层中的代码
在这里插入图片描述

3.2 如何解耦

之前我们在编写代码时,需要什么对象,就直接new一个就可以了。 这种做法呢,层与层之间代码就耦合了,当service层的实现变了之后, 我们还需要修改controller层的代码。
在这里插入图片描述
那应该怎么解耦呢?

  • 首先不能在EmpController中使用new对象。代码如下:
    在这里插入图片描述
    但是这样没有new一个EmpService对象的话,就意味着没有业务层对象,会导致程序运行时报错,那怎么解决呢?
    那就涉及到两个概念:控制反转依赖注入,解决的思路是:
    1.提供一个容器,容器中存储一些对象(例:EmpService对象)
    2.controller程序从容器中获取EmpService类型的对象

3.3 控制反转与依赖注入

3.3.1 控制反转

Inversion Of Control,简称IOC。就是将bean对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象,这种思想称为控制反转。

对象的创建权由程序员主动创建转移到容器(由容器创建、管理对象)。这个容器称为:IOC容器或Spring容器

3.3.2 依赖注入

Dependency Injection,简称DI。IOC容器为应用程序提供运行时,所依赖的资源,称之为依赖注入。

程序运行时需要某个资源,此时容器就为其提供这个资源。

例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象

3.3.3 bean对象

由IOC容器中创建和管理的对象

3.3.4 IOC和DI的代码实现

完成Controller层、Service层、Dao层的代码解耦
在这里插入图片描述
解耦思路:

第1步:在Controller层、Service层的实现类中删除new对象的代码。
在这里插入图片描述

第2步:在Controller层、Service层的实现类中添加Spring提供的注解:@Autowired ,程序运行时,IOC容器自动为Controller程序注入依赖的Service层对象,自动为Service程序注入依赖的Dao层对象。
在这里插入图片描述
第3步:在Service层、dao层的实现类中添加Spring提供的注解:@Component ,将Service层及Dao层的实现类交给IOC容器管理
在这里插入图片描述
完整代码
删除Controller层、Service层中new对象的代码,使用Spring提供的注解:@Component@Autowired,除了@Component注解,也可以使用@Component的衍生注解,可以更好的标识web应用程序开发当中,bean对象到底归属于哪一层。

  • @Controller (标注在控制层类上)
  • @Service (标注在业务层类上)
  • @Repository (标注在数据访问层类上,由于与mybatis整合,用的少)

Controller层

第一种写法,使用 @RestController注解

@RestController //@RestController = @Controller + @ResponseBody
public class EmpController {

    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpService empService ;

    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }
}

第二种写法,使用 @Controller注解

@Controller
public class EmpController {

    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpService empService ;

    @RequestMapping("/listEmp")
    public Result list(){
        //1. 调用service, 获取数据
        List<Emp> empList = empService.listEmp();

        //3. 响应数据
        return Result.success(empList);
    }
}

Service层

第一种写法,使用 @Component注解

@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpServiceA implements EmpService {

    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpDao empDao ;

    @Override
    public List<Emp> listEmp() {
        //1. 调用dao, 获取数据
        List<Emp> empList = empDao.listEmp();

        //2. 对数据进行转换处理 - gender, job
        empList.stream().forEach(emp -> {
            //处理 gender 1: 男, 2: 女
            String gender = emp.getGender();
            if("1".equals(gender)){
                emp.setGender("男");
            }else if("2".equals(gender)){
                emp.setGender("女");
            }

            //处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
            String job = emp.getJob();
            if("1".equals(job)){
                emp.setJob("讲师");
            }else if("2".equals(job)){
                emp.setJob("班主任");
            }else if("3".equals(job)){
                emp.setJob("就业指导");
            }
        });
        return empList;
    }
}

第二种写法,使用 @Service注解

@Service
public class EmpServiceA implements EmpService {

    @Autowired //运行时,从IOC容器中获取该类型对象,赋值给该变量
    private EmpDao empDao ;

    @Override
    public List<Emp> listEmp() {
        //1. 调用dao, 获取数据
        List<Emp> empList = empDao.listEmp();

        //2. 对数据进行转换处理 - gender, job
        empList.stream().forEach(emp -> {
            //处理 gender 1: 男, 2: 女
            String gender = emp.getGender();
            if("1".equals(gender)){
                emp.setGender("男");
            }else if("2".equals(gender)){
                emp.setGender("女");
            }

            //处理job - 1: 讲师, 2: 班主任 , 3: 就业指导
            String job = emp.getJob();
            if("1".equals(job)){
                emp.setJob("讲师");
            }else if("2".equals(job)){
                emp.setJob("班主任");
            }else if("3".equals(job)){
                emp.setJob("就业指导");
            }
        });
        return empList;
    }
}

Dao层

第一种写法,使用 @Component注解

@Component //将当前对象交给IOC容器管理,成为IOC容器的bean
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}

第二种写法,使用 @Repository注解

@Repository
public class EmpDaoA implements EmpDao {
    @Override
    public List<Emp> listEmp() {
        //1. 加载并解析emp.xml
        String file = this.getClass().getClassLoader().getResource("emp.xml").getFile();
        System.out.println(file);
        List<Emp> empList = XmlParserUtils.parse(file, Emp.class);
        return empList;
    }
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值