【spring】一、spring的诞生:耦合、工厂和IOC

本文深入探讨耦合与工厂模式,解析它们如何影响编程,并引出Spring的核心概念——IOC。通过实例说明Spring如何作为一个工厂,帮助创建和管理对象,实现程序的高效、低耦合。
摘要由CSDN通过智能技术生成
  • 本篇首先展示在没有spring时编程会遇到的一些痛点,引出spring
  • 介绍了耦合、工厂等概念
  • 可以更好的理解spring的诞生,以及spring都解决了哪些问题,spring的优势
  • 不涉及spring的具体使用,不想看的可以直接跳过本篇
  • 目录介绍:
    • 0 干货区:全文重点提炼,第一次看的可以直接跳过,之后当作工具栏回顾
    • 1 耦合:介绍耦合的概念、解耦的目标、思路和方法
      • 1.1 耦合是什么
      • 1.2 解耦
    • 2 工厂:举例了解工厂的概念和作用,理解spring中的工厂
      • 2.1 理解工厂
      • 2.2 工厂与spring
    • 3 小结

0 干货区

  • 耦合:
    • 概念:编译时的程序间依赖关系,包括类间的、方法间的依赖
    • 解耦的目标:编译时不依赖,运行时才依赖
    • 解耦的思路:
      • 通过反射来创建对象,而避免使用new关键字
      • 通过读取配置文件来获取要创建对象的全限定类型
      • 使用工厂来制造对象,减少重复代码
  • 工厂:
    • 目标:流水化生产对象
    • 作用:减少重复代码
    • spring:在某方面来说,spring就是一个帮我们创建对象并放入容器中保管的工厂
  • 总结:拥有spring,我们就能高效简洁的编写出低耦合的优质代码

1、耦合

1.1 耦合是什么

  • 在编程中,有一个常见术语:耦合
  • 耦合度低,是衡量程序编码是否优秀的标准之一
  • 那么什么是耦合呢
    • 很多解释是:程序间的依赖关系。包括类之间的依赖、方法间的依赖等
      • 那么像我这样刚学习的小白,就会很困惑,类和方法之间的依赖不是很常见吗?可以说,没有依赖是不可能写出程序的
    • 所以学习之后,我认为应该更精准的定义一下:
      • 耦合指的是编译时的程序间依赖关系
  • 还是像我这样的小白,可能不能很好的理解这个意思,没关系,我们先来看一个程序
// 使用jdbc连接数据库
// 第一步:注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
  • 一行很简单的注册jdbc数据库驱动的代码,这里有一个重点
    • 使用这行代码的前提是什么:引入com.mysql.jdbc的jar包。
    • 不论是eclipse还是IDEA也好,如果没有引入该jar包,这一行代码肯定会变红
    • 也就是说:无法编译,即,此处依赖Driver类,有了耦合
  • 那么我们修改一下这行代码
// 使用jdbc连接数据库
// 第一步:注册驱动
Class.forName("com.mysql.jdbc.Driver");
  • 好,我们来看下第二段代码
    • 和第一段代码有什么区别呢
      • 1、使用了反射,而不是new的方式来创建一个对象
        • new依赖一个具体的类,而反射只是依赖一个字符串
      • 2、没有相关jar包,可以编译通过(即解耦)
    • 相同之处
      • 1、都是在注册驱动
      • 2、都需要jdbc的Jar包
        • 只不过第一段是在编译时就需要
        • 第二段是运行时需要,否则会报错

1.2 解耦

  • 通过1.1节,明确了耦合的概念是:编译时的程序间依赖关系
  • 那么降低耦合的目标就是:编译时不依赖,运行时才依赖
  • 通过1.1节的例子,也很明确地给出了解耦的思路:
    • 使用反射来创建对象,而避免使用new关键字
  • 好,确定了这个思路之后,再看看1.1节的例子,有没有觉得第二段程序哪里还是有点问题呢?
    • 注意,第二段里的那串字符串,是在程序内写死的
    • 在程序里写死,意味着非常的不灵活,需要改变
  • 继续优化程序,由此可以引出第二条解耦的思路:
    • 通过读取配置文件来获取要创建对象的全限定类型
  • 举个例子,你要办个party,通过经纪人老王联系到明星美丽来表演
// 这是接口Star,全限定类名:com.star.Star
public interface Star {
	public void sing();  // 明星要会唱歌
}
// 这是Star的实现类Meili,全限定类名:com.star.impl.Meili
public class Meili implements Star{
	public void sing() {
		System.out.println("Meili is singing.");
	}
}

// 这是接口Agent,全限定类名:com.agent.Agent
public interface Agent {
	public void sing(Star star);
}
// 这是Agent的实现类Laowang,全限定类名:com.agent.impl.Laowang
public class Laowang implements Agent {
	private Star meili = new Meili();
	public void sing() {  // 叫明星来唱歌
		meili.sing;
	}
}
  • 美丽和老王都准备好了,接下来准备我们的配置类party.properties
meili=com.star.impl.Meili
laowang=com.agent.impl.Laowang
  • OK,终于可以开party了
public class Party {
    public static void main(String[] args) {
        Agent laowang = null;
        try {
            // 1.创建配置文件读取类对象
            Properties props = new Properties();
            // 2.获取properties文件的流对象
            props.load(Party.class.getClassLoader().getResourceAsStream("party.properties"));
            // 3.获得laowang这个key对应的value,即全限定类名
            String beanPath = props.getProperty("laowang");
            System.out.println(beanPath);
            // 4.通过反射调用默认构造函数创建对象,因为反射返回的是Object,强转为Laowang
            laowang = (Laowang) Class.forName(beanPath).newInstance();
            // 5.通过老王让美丽唱歌
            laowang.sing();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行一下,获得的结果是:

com.agent.impl.Laowang
Meili is singing.
  • 看了上面这段程序,肯定会有人内心崩溃吐槽:这不是还是各种使用了new吗?!!!
  • OK,我们来分开分析一下应该出现new的三个地方
    • 1、Party类中读取配置文件的props对象,使用了new
      • 首先我们必须声明,解耦只能做到尽量降低耦合度,并不可能完全消除耦合,所以显然是无法在程序中消除所有new关键字的
      • 显然,props的new,就是无法避免的情况
    • 2、Party类中的经纪人laowang对象
      • 这里我们成功消除了new,来创建一个laowang对象,鼓掌!
      • 那么还有什么问题呢,laowang这个对象名称,还是在程序中写死了是吧?
      • 这就类似于解耦,也是尽量降低而做不到完全消除
      • 当然后面讲到spring,会发现通过一种默认方式其实还是可以做到的(本篇不讲)
    • 3、Laowang类中的明星meili对象
      • 这里依然是通过new来创建了一个meili对象
      • 但是我们明明在配置文件中也配置了Meili的全限定类名
      • 其实这里也可以通过读取配置文件然后反射出meili对象,去掉new
      • 那么会发生什么呢,我们要再创建一个props,获取流,打开配置文件,读取value。。。
      • 优秀的程序应该尽量减少重复代码
      • 所以显然,我们可以把读取配置文件、反射生成对象的这部分功能,单独抽出来,编写一个专门用于制造对象的工厂类

2、工厂

2.1 理解工厂

  • 这里涉及到一个设计模式:工厂模式
    • 工厂模式的具体内容,本篇不细讲,后面有空专门写一篇工厂模式再贴上来
    • 这里大概意会一下工厂模式的作用就行了
  • 工厂,通过现实里的概念也很好理解:
    • 有投入、有产出
    • 流水化作业
    • 标准产品
  • 映射到编程里,可以对应以下几个概念:
    • 有传入参数,有返回值
    • 一个工具类
    • 返回值要具有通用性
  • 回到我们的例子里,现在要建造一个专门用于生产对象的工厂类啦
public class ObjectFactory {
    // 定义一个properties对象。定义为static,避免不必要的内存开销
    private static Properties props;

    // 使用静态代码块为props对象赋值
    static {
        try {
            // 实例化对象
            props = new Properties();
            // 获取properties文件的流对象
            props.load(PartyFactory.class.getClassLoader().getResourceAsStream("party.properties"));
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化properties失败!");
        }
    }

    /**
     * 生成对象方法。定义为static,可以通过类名直接调用
     * @param beanName 要生成的对象名称,配置文件中的key值
     * @return 返回生成的对象,定义为Object类型,具有通用性
     */
    public static Object getBean(String beanName) {
        Object bean = null;
        try {
            // 获取key对应的value,即全限定类名
            String beanPath = props.getProperty(beanName);
            // 通过反射生成对象
            bean = Class.forName(beanPath).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
}
  • 再调整一下Laowang类
public class Laowang implements Agent {
    public void sing() {
    	// 使用对象工厂获得meili对象,去掉new实现解耦
    	Star meili = (Meili)ObjectFactory.getBean("meili");
        meili.sing();
    }
}
  • 又可以开party啦
public class Party {
    public static void main(String[] args) {
        // 通过对象工厂类获取laowang对象
        Agent laowang = (Laowang)ObjectFactory.getBean("laowang");
        laowang.sing();
    }
}
  • 运行一下,结果是:
Meili is singing.
  • 在上例的代码中,可以看到我们完全把创建对象的工作交给了工厂类来做
  • 这个就叫做:IOC(控制反转)
  • IOC是spring的核心概念之一
  • 讲到这里,大家可能隐隐约约猜到spring的本质了
    • spring就是一个帮我们创建对象的工厂
    • 是这样吗?

2.2 工厂与spring

  • 在上面举的例子里,不知道大家有没有发现一个问题
    • 工厂类中,getBean这个方法,调用一次,就创建一个独立的对象
    • 我们可以在main中把做一个无限的for循环,很快就会内存溢出导致程序挂掉
    • 而实际上,在Meili类和Laowang类里没有其他有线程安全问题的成员变量的情况下,其实我们只需要工厂类创建一个对象,就可以反复循环使用了
  • 顺着这个思路,也就是说
    • 配置文件中的每条配置,也就说每个数据类型,都只创建一个具体变量
    • 变量创建出来后,需要找个容器保存下来,以备后续调用
    • 当外部再调用获取变量的方法时,直接去容器里查找
  • 我们再修改一下工厂类
public class ObjectFactory {
    // 定义一个properties对象
    private static Properties props;
    // 定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
    private static Map<String, Object> beans;

    // 使用静态代码块为props对象赋值并填充容器
    static {
        try {
            // 1.实例化props,读取配置文件
            props = new Properties();
            props.load(ObjectFactory.class.getClassLoader().getResourceAsStream("party.properties"));

            // 2.填充容器
            beans = new HashMap<String, Object>();
            // 2.1取出配置文件中所有的keys
            Enumeration keys = props.keys();
            // 2.2遍历枚举
            while (keys.hasMoreElements()) {
                // 2.3取出每个key
                String key = keys.nextElement().toString();
                // 2.4根据key获取value
                String beanPath = props.getProperty(key);
                // 2.5反射创建对象
                Object value = Class.forName(beanPath).newInstance();
                // 2.6把key和value存入容器之中
                beans.put(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据bean的名称获取bean对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return beans.get(beanName);
    }
}
  • 继续开party
public class Party {
    public static void main(String[] args) {
        // 通过对象工厂类获取laowang对象
        Agent laowang = (Laowang)ObjectFactory.getBean("laowang");
        laowang.sing();
        System.out.println(laowang);

        Agent laowang1 = (Laowang)ObjectFactory.getBean("laowang");
        System.out.println(laowang1);
    }
}
  • 注意在main中,我们二次调用getBean去获取laowang对象。运行得到:
Meili is singing.
com.agent.impl.Laowang@74a14482
com.agent.impl.Laowang@74a14482
  • laowang和laowang1这两个变量的地址一模一样,可见此时我们获得的都是同一个对象
  • 通过这个例子,联想到spring,大家应该可以猜得到:
    • 在某方面来说,spring就是一个帮我们创建对象并放入容器中保管的工厂

3、小结

  • 由以上例子可知,我们把创建对象的权力交给了工厂
  • 而当spring出现成为这个工厂时,我们就把创建对象的权力交给了spring
  • 这就是spring中的IOC,控制反转
    • 我们把对对象的控制,反转交给了spring来负责,而不是我们自己
  • 拥有了spring这个工具,我们就可以专注于系统逻辑的编写
    • 高效简洁的编写出低耦合的优质代码
  • 当然又有人要提问了
    • 配置中的每个数据类型都只建了一份,那我们就是想要建多份怎么办?
    • 可能还有别的问题等等
  • 但是,都不用担心!这些要求spring都能满足你!
  • 本篇就不扩展讲了,能大概理解到spring的作用和优势就OK
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值