控制反转 IOC
一步步道来
一:OCP 开闭原则
1、interface统一方法调用
2、只有一段代码中没有new的出现,才能保持代码的相对稳定,才能逐步实现OCP
3、但代码中总是会存在不稳定,所以解决办法是:隔离这些不稳定,保证其他的代码是稳定的
4、工厂模式+反射:统一对象的实例化。但这并不是IOC
5、工厂模式+反射,实例化后放缓存提供性能
工厂模式+反射
public class Main {
public static void main(String[] args) throws Exception {
String name=Main.getInput();
IBase iBase= Factory.getUser(name);
iBase.print();
}
private static String getInput(){
System.out.println("请输入");
Scanner scanner=new Scanner(System.in);
return scanner.nextLine();
}
}
package com.company.test;
public class Factory {
public static IBase getUser(String name) throws Exception {
//变化导致不稳定,用户输入-选择就是变化,所以这里是隔离了变化
String classStr="com.company.test."+name;
//反射
Class <?> cla=Class.forName(classStr);
Object obj=cla.newInstance();
return (IBase)obj;
}
}
.
二、依赖注入 DI
依赖注入是IOC其中的一种表现
一个A类要依赖于注入的B类才能实现作用,就是A依赖于B
public Class A{
public void fun(){
B b=new B();
b.print();
}
如果这个B类是注入的,那就叫依赖注入
注入的类一定是实现接口的,不然就违法了OPC,实例的名称也可以直接是接口名称(需同接口下只有一个在容器里)
public Class A{
private ICa ca; //属性注入;仅演示,并不是这样就直接注入成功
public void fun(){
ca.print();
}
常见的依赖注入方式:
- 属性注入
- 构造注入
.
三、容器
容器的作用是装配对象;将各种注入,类的衔接,装配在一起,又起到隔离风险的效果
public void user(){
Iface ic=new C();
A a=new A(ic); //构造注入
A a1=new A();
a1.setIc(ic); //属性注入
}
.
四、控制反转 IOC
上面一堆的铺垫介绍,合并起来就是控制反转
原来是在A类中实例化C类并调用方法,那A控制C,A是控制方
public Class A{
private C c=new C();
public void fun(){
c.print();
}
由于使用了依赖注入,C不再由A控制,不引入C,也不实例化C。引入的是接口 ca自动匹配名称(spring的约定)
public Class A{
private ICa ca;
public void fun(){
ca.print();
}
通过外部传入依赖,而不是自己创建依赖。那么问题来了,谁把依赖传给他们,答案是IoC容器。
类A依赖类C的强耦合关系可以在运行时通过容器建立,也就是说把创建C实例的工作移交给容器,类A只管使用就可以。控制方由A变成了容器,实现了控制反转
.
为了理解这几个概念,我们还是用上面汽车的例子。只不过这次换成代码。我们先定义四个Class,车,车身,底盘,轮胎。然后初始化这辆车,最后跑这辆车。代码结构如下:
![](https://img.haomeiwen.com/i1957695/ee7cf35871780258.jpg)
这样,就相当于上面第一个例子,上层建筑依赖下层建筑——每一个类的构造函数都直接调用了底层代码的构造函数。假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:
![](https://img.haomeiwen.com/i1957695/a752d321d5825372.jpg)
由于我们修改了轮胎的定义,为了让整个程序正常运行,我们需要做以下改动:
![](https://img.haomeiwen.com/i1957695/36dc14e789651c65.jpg)
由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数!在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。
所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。这里我们用构造方法传递的依赖注入方式重新写车类的定义:
![](https://img.haomeiwen.com/i1957695/f70a31173cefb58b.jpg)
这里我们再把轮胎尺寸变成动态的,同样为了让整个系统顺利运行,我们需要做如下修改:
![](https://img.haomeiwen.com/i1957695/293768e22fb88634.jpg)
只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。
如果我们要写Car类的单元测试,就只需要Mock一下Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。
其他:依赖倒置 DIP
高层模块(抽象模块)不应该依赖低层模块,两者都应该依赖抽象
抽象不应该依赖细节
细节应该依赖抽象
依赖注入:一个A类要依赖于注入的B类才能实现作用
依赖倒置:一个A类要依赖于 倒着注入(不限于注入)的B类才能使用,倒着注入就是反射
依赖倒置核心就是:要面向抽象去编程
类似如下代码就是依赖倒置,没有直接注入,而是接口+反射 实现了倒置注入
public static void main(String[] args) throws Exception {
String name=Main.getInput();
IBase iBase= Factory.getUser(name);
iBase.print();
}