一、已经知道的内容如下:
-
什么是构造函数?
-
Java 构造函数,也叫构造方法,是Java 类中的一种特殊方法,方法名与类名相同,一般用来初始化成员变量,当要生成一个类的对象(实例)的时候就会调用构造函数,如果类中没有显示声明类的构造方法,Java 会自动生成一个默认的不带参数的空构造函数。(接口类和抽象类没有构造函数)
-
什么是接口?
-
接口是功能的集合,同样可以看做是一种数据类型,是一种特殊的抽象类。接口中的方法也全部都是抽象的,直接new 接口来调用是没有意义的,Java 也不允许这样。当然接口也可以被实现,实现接口的类需要重写接口中的方法,并完成具体的逻辑(是否体现多态?)
-
接口中成员的特点:
-
接口中定义变量,但变量必须有固定的修饰符修饰--public static final,所以接口中的变量也称为常量(需要和enmu做对比),其值不能改变
-
接口中定义方法 ,同样也有固定的修饰符--public abstract
-
接口不能创建对象
-
子类必须覆盖接口中所有抽象方法(实现接口方法),才可以实例化,子类也可以有自己的方法,但是通过接口指向创建的实例不能调用子类的方法,这也是多态的缺点,无法直接访问子类特有的成员及方法
-
接口的思想:方便后期维护,只需要关心接口中的方法即可。集合体系中大量使用了接口
-
接口的优点:①类与接口的关系,实现关系,而且是多实现,打破了一个类只能继承一个父类的限制;②对外提供规则③降低了程序的耦合性(可以实现模块化开发,每个人实现自己的模块,提高开发效率)
-
接口和抽象类的区别:详见笔记 深入理解abstract class和interface
二、回到正题来
-
面向接口编程
- Spring IoC 的原理概述
通过上面的例子分析,我们不难发现。List 的实现类有很多(ArrayList、LinkedList 等),我们可以根据需求进行选择合适的实现类,灵活运用,这也采用面向接口的一大优势,降低代码耦合度(只需要改变指向List 的接口,而不需要改变List 的实现类 )。再看,在日常开发中我们经常会遇到这种情况:
public class BookServiceImpl {
private BookDaoImpl bookDaoImpl;
public void oldCode() {
// 原来的做法
bookDaoImpl = new BookDaoImpl();
bookDaoImpl.getAllCategories();
}
}
//----------------new---------------------
public class BookServiceImpl {
// interface
private BookDao bookDao;
public void newCode() {
// 变为面向接口编程
bookDao = new BookDaoImpl();
bookDao.getAllCategories();
}
}
BookServiceImpl类中由原来直接与BookDaoImp打交互变为BookDao,即使BookDao最终实现依然是BookDaoImp,这样的做的好处是显而易见的,所有调用都通过接口bookDao来完成,而接口的真正的实现者和最终的执行者就是bookDaoImpl,当替换bookDaoImpl类,也只需修改bookDao指向新的实现类。
虽然上述的代码在很大程度上降低了代码的耦合度,但是代码依旧存在入侵性和一定程度的耦合性,比如在修改bookDao的实现类时,仍然需求修改BookServiceImpl的内部代码,当依赖的类多起来时,查找和修改的过程也会显得相当糟糕,因此我们仍需要寻找一种方式,它可以令开发者在无需触及BookServiceImpl内容代码的情况下实现修改bookDao的实现类,以便达到最低的耦合度和最少入侵的目的。实际上存在一种称为反射的编程技术可以协助解决上述问题,反射是一种根据给出的完整类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才决定到底是哪一种对象,因此可以这样假设,在某个配置文件,该文件已写好bookDaoImpl类的完全限定名称,通过读取该文件而获取到bookDao的真正实现类完全限定名称,然后通过反射技术在运行时动态生成该类,最终赋值给bookDao接口,也就解决了刚才的存在问题,这里为简单演示,使用properties文件作为配置文件,className.properties如下:
bookDao.name=com.zejian.spring.dao.BookDaoImpl
获取该配置文件信息动态为bookDao生成实现类:
public class BookServiceImpl implements BookService {
// 读取配置文件的工具类
PropertiesUtil propertiesUtil = new PropertiesUtil("conf/className.properties");
private BookDao bookDao;
public void DaymicObject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
// 获取完全限定名称
String className = propertiesUtil.get("bookDao.name");
// 通过反射
Class c = Class.forName(className);
// 动态生成实例对象
bookDao = (BookDao) c.newInstance();
}
}
的确如我们所愿生成了bookDao的实例,这样做的好处是在替换bookDao实现类的情况只需修改配置文件的内容而无需触及BookServiceImpl的内部代码,从而把代码修改的过程转到配置文件中,相当于BookServiceImpl及其内部的bookDao通过配置文件与bookDao的实现类进行关联,这样BookServiceImpl与bookDao的实现类间也就实现了解耦合,当然BookServiceImpl类中存在着BookDao对象是无法避免的,毕竟这是协同工作的基础,我们只能最大程度去解耦合。
了解了上述的问题再来理解IOC就显得简单多了。Spring IOC 也是一个java对象,在某些特定的时间被创建后,可以进行对其他对象的控制,包括初始化、创建、销毁等。简单地理解,在上述过程中,我们通过配置文件配置了BookDaoImpl实现类的完全限定名称,然后利用反射在运行时为BookDao创建实际实现类,包括BookServiceImpl的创建,Spring的IOC容器都会帮我们完成,而我们唯一要做的就是把需要创建的类和其他类依赖的类以配置文件的方式告诉IOC容器需要创建那些类和注入哪些类即可。Spring通过这种控制反转(IoC)的设计模式促进了松耦合,这种方式使一个对象依赖其它对象时会通过被动的方式传送进来(如BookServiceImpl被创建时,其依赖的BookDao的实现类也会同时被注入BookServiceImpl中),而不是通过手动创建这些类。我们可以把IoC模式看做是工厂模式的升华,可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在配置文件(XML)中给出定义的,然后利用Java的反射技术,根据XML中给出的类名生成相应的对象。从某种程度上来说,IoC相当于把在工厂方法里通过硬编码创建对象的代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性,更是达到最低的耦合度,因此我们要明白所谓为的IOC就将对象的创建权,交由Spring完成,从此解放手动创建对象的过程,同时让类与类间的关系到达最低耦合度。
注:以上第二条参考博文 关于Spring IOC (DI-依赖注入)你需要知道的一切