Spring学习记录之Spring启示录

发布时间 2023-12-22 10:06:03作者: zhao-XH

Spring学习记录之Spring启示录


前言

这篇文章是我第二次学习b站老杜spring相关课程所进行的学习记录,算是对课程内容及笔记的二次整理,以自己的理解方式进行二次记录,其中理解可能存在错误,欢迎且接受各位大佬们的批评指正;

学习视频地址:https://www.bilibili.com/video/BV1Ft4y1g7Fb/

视频配套笔记:https://www.yuque.com/dujubin/ltckqu/kipzgd?singleDoc# 《Spring6》 密码:mg9b


目录


Spring启示录

一、阅读以下代码

UserController

public class UserController {

    private UserService userService = new UserServiceImpl();

    public void login(){
        String username = "admin";
        String password = "123456";
        boolean success = userService.login(username, password);
        if (success) {
            // 登录成功
        } else {
            // 登录失败
        }
    }
}

UserService

省略...

UserServiceImpl

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImplForMySQL();

    public boolean login(String username, String password) {
        User user = userDao.selectByUsernameAndPassword(username, password);
        if (user != null) {
            return true;
        }
        return false;
    }
}

UserDao

省略...

UserDaoImplForMySQL

public class UserDaoImplForMySQL implements UserDao {
    public User selectByUsernameAndPassword(String username, String password) {
        // 连接MySQL数据库,根据用户名和密码查询用户信息
        return null;
    }
}

二、分析上述代码存在的问题

如果用户表从MySql迁移到了Oracle(模拟开发需求变更)

我们需要再提供一个UserDao实现类UserDaoImplForOracle

public class UserDaoImplForOracle implements UserDao {
    public User selectByUsernameAndPassword(String username, String password) {
        // 连接Oracle数据库,根据用户名和密码查询用户信息
        return null;
    }
}

做完这一步之后,又因为我们在UserServiceImpl中引入了UserDaoImplForMySQL实现类对象,我们也需要切换其为UserDaoImplForOracle实现类对象。

很明显,以上的开发需求只是进行功能的扩展,添加了一个新的类UserDaoImplForOracle来应付数据库的变化(这样的操作在实际开发中是很常见的),但是对功能扩展的过程中引起了类间的连锁反应,这样的代码是耦合的、不易维护的。

其实这里有了解过面向对象编程的六大原则同学,也能看出这里违反了OCP开闭原则DIP依赖倒置原则

三、引出三种编程思想

这里借老杜的解释再阐述一下

① OCP开闭原则

开闭原则是这样说的:在软件开发过程中应当对扩展开放,对修改关闭。也就是说,如果在进行功能扩展的时候,添加额外的类是没问题的,但因为功能扩展而修改之前运行正常的程序,这是忌讳的,不被允许的。因为一旦修改之前运行正常的程序,就会导致项目整体要进行全方位的重新测试。这是相当麻烦的过程。导致以上问题的主要原因是:代码和代码之间的耦合度太高。如下图所示:

img

可以很明显的看出,上层是依赖下层的。UserController依赖UserServiceImpl,而UserServiceImpl依赖UserDaoImplForMySQL,这样就会导致下面只要改动上面必然会受牵连(跟着也会改),所谓牵一发而动全身。这样也就同时违背了另一个开发原则:依赖倒置原则

② DIP依赖倒置原则

依赖倒置原则(Dependence Inversion Principle),简称DIP,主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务

你可能会说,上面的代码已经面向接口编程了呀:

img

确实已经面向接口编程了,但对象的创建是:new UserDaoImplForOracle()显然并没有完全面向接口编程,还是使用到了具体的接口实现类。什么叫做完全面向接口编程?什么叫做完全符合依赖倒置原则呢?请看以下代码:

img

如果代码是这样编写的,才算是完全面向接口编程,才符合依赖倒置原则。那你可能会问,这样userDao是null,在执行的时候就会出现空指针异常呀。你说的有道理,确实是这样的,所以我们要解决这个问题。解决空指针异常的问题,其实就是解决两个核心的问题

  • 第一个问题:谁来负责对象的创建。【也就是说谁来:new UserDaoImplForOracle()/new UserDaoImplForMySQL()】
  • 第二个问题:谁来负责把创建的对象赋到这个属性上。【也就是说谁来把上面创建的对象赋给userDao属性】

如果我们把以上两个核心问题解决了,就可以做到既符合OCP开闭原则,又符合依赖倒置原则。

很荣幸的通知你:Spring框架可以做到。

在Spring框架中,它可以帮助我们new对象,并且它还可以将new出来的对象赋到属性上。换句话说,Spring框架可以帮助我们创建对象,并且可以帮助我们维护对象和对象之间的关系。比如:

img

Spring可以new出来UserDaoImplForMySQL对象,也可以new出来UserDaoImplForOracle对象,并且还可以让new出来的dao对象和service对象产生关系(产生关系其实本质上就是给属性赋值)。

很显然,这种方式是将对象的创建权/管理权交出去了,不再使用硬编码的方式了。同时也把对象关系的管理权交出去了,也不再使用硬编码的方式了。像这种把对象的创建权交出去,把对象关系的管理权交出去,被称为控制反转。

③ IoC控制反转

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计思想,可以用来降低代码之间的耦合度,符合依赖倒置原则。

控制反转的核心是:将对象的创建权交出去,将对象和对象之间关系的管理权交出去,由第三方容器来负责创建与维护。即不在程序中采用硬编码的方式来new对象了(把new对象的权力交出去),同时也不在程序中采用硬编码的方式来维护对象之间的关系了(对象之间关系的维护权也交出去了)。

控制反转常见的实现方式:依赖注入(Dependency Injection,简称DI)(补充强调:IoC是面向对象编程中的一种设计思想,DI是实现这种思想的一种方式。)

个人补充:在上述示例中我们讲到,不在类中自己去new对象,去使用该对象引用时一定会报空指针异常。所以我们要在使用该对象时让Spring自动将需要的对象实例赋值给改引用。一般情况下,我们给类中对象属性赋值时,一般采用两种方式,即一是通过构造方法给改对象引用赋值,二是调用该对象实例的set方法给对象引用赋值。所以Spring其实也是通过这两种方式去操作的。

通常,依赖注入的实现又包括两种方式:

  • set方法注入

  • 构造方法注入

而Spring框架就是一个实现了IoC思想的框架。

IoC可以认为是一种全新的设计模式,但是理论和时间成熟相对较晚,并没有包含在GoF中。(GoF指的是23种设计模式)

四、如何理解"依赖注入"

依赖注入中"依赖"是什么意思?

依赖其实就是对象和对象之间的关系。A<=>B<=>C

依赖注入中"注入"是什么意思?

注入是一种手段,通过这种手段,可以让对象之间产生依赖关系。

五、总结

到这里,我们就引出了学习Spring大概我们要解决的问题是什么,以及为什么要学Spring。很显然接下来我们就要学习哪些方法手段(代码写法)可以把对象的创建权交出去,把对象关系的管理权交出去,实现IoC控制反转。