依赖注入
什么是依赖?
依赖是类与类之间的联系,依赖关系表示一个类依赖于另一个类的定义,通俗来讲,就是一种需要;例如一个人(person)可以买车(car)和房子(house),Person类依赖于Car类和House类。
public static void main(String[] args){
Person person = new Person();
person.buy(new House());
person.buy(new Car());
}
static class Person{
//表示依赖House
public void buy(House house){ }
//表示依赖car
public void buy(Car car){ }
}
static class House{
}
static class Car{
}
依赖倒置(Dependency inversion principle)
依赖倒置是面向对象设计领域的一种软件设计原则
软件设计有六大设计原则,合称SOLID
1.单一职责原则(Single Responsibility Principle,简称SRP)
- 核心思想:应该有且仅有一个原因引起的类的变更
- 问题描述:假如有类Class完成职责T1,T2,当职责T1或T2有变更需要修改时,有可能影响到该类的另外一个职责正常工作。
- 好处:类的复杂度降低、可读性提高、可维护性提高、扩展性提高、降低了变更引起的风险
- 需注意:单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可以度量的,因项目和环境而异
2.里氏替换原则(Liskov Substitution Principle,简称LSP)
- 核心思想:在使用基类的地方可以任意使用其子类,能保证子类完美替换基类
- 通俗来讲:只要父类能出现的地方子类就能出现,反之,父类则未必能胜任
- 好处:增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行
- 需注意:如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚合、组合等关系代替继承。
3.依赖倒置原则(Dependence Inversion Principle,简称DIP)
- 核心思想:高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象
- 说明:高层模块就是调用端,底层模块就是具体实现类。抽象就是指接口或者抽象类。细节就是实现类。
- 通俗来讲:依赖倒置原则本质上就是通过抽象(接口或者抽象类)使各个类或者模块的实现彼此独立,互补影响,实现模块间的松耦合。
- 问题描述:类A直接依赖B,假如要将类A改为依赖C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险
- 解决方案:将类A修改为依赖接口interface,类B和类C各自实现接口interface,类A通过接口interface间接与类B或者类C发生联系,则会大大降低修改类A的几率。
- 好处:依赖倒置的好处在小型项目中很难体现出来。但在大中型项目中可以减少需求变化引起的工作量。使并行开发更友好。
4.接口隔离原则(Interface Segregation Principle,简称ISP)
- 核心思想:类间的依赖关系应该建立在最小的接口上
- 通俗来说:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不是要试图去建立一个很庞大的接口供所有的依赖它的类去调用。
- 问题描述:类A通过接口interface依赖B,类C通过接口interface依赖类D,如果接口interface对于类A和类B来说不是最小接口,则B和D必须去实现他们不需要的方法
- 需注意:
- 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是接口过于小,则会造成接口数量过多,是设计复杂化。所以一定要适度
- 提高内聚,减少对外交互。是接口用最少的方法去完成最多的事情
- 为依赖接口的类定制服务。只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地位一个模块提供定制服务,才能建立最小的依赖关系。
5.迪米特原则(Law of Demeter ,简称LoD)
- 核心思想:类间解耦
- 通俗来说:一个类对自己依赖的类知道的越少越好。自从我们接触编程开始,就知道了软件编程的总的原则:高内聚,低耦合。无论面向过程还是面向对象编程,只有使各个模块之间的耦合尽量的低,才能调高代码的复用率。低耦合的优点不言而喻,但是怎么样编程才能做到低耦合呢?那正是迪米特法则要去完成的。
6.开放封闭原则(Open Close Principle,简称:OCP)
- 核心思想:尽量通过扩展软件实体来解决需求变化,而不是通过修改已有代码来完成变化
- 通俗来说:一个软件产品在生命周期内,都会发生变化,既然变化是一个既定的事实,我们就应该在设计的时候尽量适应这些变化,以提高项目的灵活性和稳定性。
依赖倒置的原则定义如下:
1.上层模块不应该依赖底层模块,他们都应该依赖于抽象
2. 抽象不应该依赖于细节,细节应该依赖于抽象
什么是上层模块和下层模块?
不管你承不承认,“有人的地方就有江湖”,我们说人人平等,但是对于任何一个组织机构而言,它有一定的架构的设计有职能的划分。
高层模块就是调用端,底层模块就是具体实现类。抽象就是指接口或者抽象类。细节就是实现类。
业务层属于上层模块,逻辑层和数据层自然就归类为底层
什么是抽象和细节?
抽象如其名字一样,是一件很抽象的事物。抽象往往是相对于具体而言的,具体也可以被称为细节,当然也被称为具象。
控制反转(IOC)
控制反转IOC是Inversion Of Control的缩写,意思就是对于控制权的反转。
Person自己掌握着内部mDriveable的实例化.
现在,我们可以更改一种方式。将mDriveable的实例化转移到Person的外面
public class Person{
private Driveable mDriveable;
public Person(Driveable driveable){
this.mDriveable = driveable;
}
public void goOut(){
System.out.print("出门啦");
mDriveable.drive();
}
public static void main(String[] args){
Person person = new Person(new Car());
person.goOut();
}
}
就这样,无论出行方式怎么变,Person这个类都不需要更改代码了。
在上面的代码中,Person把内部依赖的创建权力移交给类中的main()方法。也就是说Person只关心依赖提供的功能,但并不关心依赖的创建。
这种思维其实就是IOC,IOC是一种新的设计模式,它对上层模块和底层模块进行了更进一步的解耦。控制反转的意思就是反转了上层模块对于底层模块的依赖控制。
依赖注入(Dependency injection)
依赖注入,也经常被简称为DI。
为了不因为依赖的变动而去修改Person,也就是说可能再Driveable实现类的改变下而不改动Person这个类的代码,尽可能减少两者之间的耦合。我们需要用IOC模式来进行该写代码。
这个需要我们移交出对于依赖实例化的控制权,那么依赖怎么办?Person无法实例化依赖了,它就需要在外部(IOC容器)赋值给它,这个赋值动作有个专门的术语叫注入(injection),需要注意的是在IoC概念中,这个注入依赖的地方被称为IOC容器,但是在依赖注入概念中,一般被称为注射器(injector)
表达通俗一点的就是:我不想自己实例化依赖,你(injector)创建它们,然后在合适的时候注入给我
实现依赖注入的有3种方式:
1. 构造函数注入
2. setter方式注入
3. 接口注入
/**
* 接口方式注入,接口的存在,表明了一种依赖配置的能力
*/
public interface DependencySetter{
void set(Driveable driveable);
}
public class Person implements DependcySetter{
//接口方式注入
@override
public void set(Driveable driveable){
this.mDriveable = mDriveable;
}
private Driveable mDriveable;
//构造函数注入
public Person(Driveable driveable){
this.mDriveable = driveable;
}
//setter方式注入
public void setDriveable(Driveable mDriveable){
this.mDriveable = mDriveable;
}
}
java依赖注入标准
JSR-330是Java的依赖注入标准。定义了如下的术语描述依赖注入:
- A类型依赖B类型(或者说B类型被A类型依赖),则A类型成为“依赖(物)dependency”
- 运行时查找依赖的过程,成为“解析resolving”依赖
- 如果找不到依赖的实例,称该依赖是“不能满足的unsatisfied”
- 在依赖注入dependency injection 机制中,提供依赖的工具成为依赖注入器dependency injector
javax.inject
包Javax.inject 指定了获取对象的一种方法,该方法与构造器、工厂以及服务定位器(如JNDI)这些传统方法相比可以获得更好的可重用性、可测试性以及可维护性。此方法的处理过程就是大家熟知的依赖注入,它对大多数应用是非常有价值的
@Inject
注解@Inject 标识了可以注入的构造器、方法或者、字段。可以用于静态或实例成员。一个可注入的成员可以被任何访问修饰符(private、package-private、protected、public)修饰。注入顺序为构造器,字段,最后是方法。超类的字段、方法将优先于子类的字段、方法被注入。对于同一个类的字段是不区分注入顺序的,同一个类亦同。
Provider
接口Provider用于提供类型T的实例。Provider是一般情况是由注射器实现的。对于任何可注入的T而言,您也可以注入Provider.与直接注入T相比,注入Provider 使得:
- 可以返回多个实例
- 实例的返回可以延迟化或可选
- 打破循环依赖
- 可以在一个一直作用域的实例内查询一个更小作用于内的实例
@Qualifier
用于标识先顶起注解。任何人都可以定义新的限定器注解。一个限定器注解:
- 是被@Qualifier@Retention(RUNTIME)标注的,通常也被@Documented标注
- 可以拥有属性
- 可能是公共API的一部分,就像依赖类型一样,而不是类型实现那样不作为公共API的一部分。
- 如果标注了@Target可能会有一些用法限制。本规范只是指定了限定器注解可以被使用在字段和参数上,但一些注入器配置可能使用限定器注解在其他一些地方(例如方法和类)上
@Named
- 基于String的【限定器】
@Scope
- 用于标识作用域注解。一个作用域注解是被标识在包含一个可注入构造器的类上的,用于控制该类型的实例如何被注入器重用。缺省情况下,如果没有标识作用域注解,注入器将为每一次注入都创建(通过注入类型的构造器)新实例,并不重用已有实例,该实例实现应该是线程安全的。作用域实现由注入器完成
@Singleton
- 标识了注入器只实例化一次的类型。该注解不能被继承。