面向对象设计原则之依赖倒转原则
一、什么是依赖倒转原则
定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
依赖倒转原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口或抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
二、实例
小明开宝马车上路:
正常的思维:
public class BMW{
public void run(){
System.out.println("宝马车开动了....");
}
}
public class Person{
public void drive(BMW bmw){
System.out.println("小明开始开车...");
bmw.run();
}
}
public class Client{
public static void main(String[] args) {
new Person().drive(new BMW());
}
}
但是现在更改需求了,小明嫌弃宝马车不好,现在想开法拉利。那么如果在这个类的基础上更改,我们需要给司机提供一个drive(Ferrari ferrari)的方法。同时再提供一个Ferrari类,提供一个run方法。那么就存在类之间的依赖性太大,不利于类的拓展。
解决方法:
2.1、构造函数传递依赖
public interface IDriver {
//构造函数传递依赖
public void drive();
}
public interface ICar {
public void run();
}
public class BMW implements ICar {
@Override
public void run() {
System.out.println("开着宝马上路!");
}
}
public class Ferrari implements ICar {
@Override
public void run() {
System.out.println("开着法拉利上路!");
}
}
public class Person implements IDriver {
//构造函数传递依赖
private ICar car;
public Person(ICar car){
this.car = car;
}
@Override
public void drive() {
System.out.println("小明开始开车。。。");
car.run();
}
}
public class Client {
public static void main(String[] args) {
//构造函数传递依赖
ICar car = new BMW();
IDriver driver = new Person(car);
driver.drive();
ICar car1 = new Ferrari();
IDriver driver = new Person(car1);
driver.drive();
}
2.2、setter方法传递依赖
public interface IDriver {
//setter方法传递依赖
public void drive();
public void setCar(ICar car);
}
public interface ICar {
public void run();
}
public class BMW implements ICar {
@Override
public void run() {
System.out.println("开着宝马上路!");
}
}
public class Ferrari implements ICar {
@Override
public void run() {
System.out.println("开着法拉利上路!");
}
}
public class Person implements IDriver {
//setter方法传递依赖
private ICar car;
@Override
public void setCar(ICar car) {
this.car = car;
}
@Override
public void drive() {
System.out.println("小明开始开车。。。");
car.run();
}
}
public class Client {
public static void main(String[] args) {
//setter方法传递依赖
ICar car = new Ferrari();
IDriver driver = new Person();
driver.setCar(car);
driver.drive();
ICar car2 = new BMW();
IDriver driver2 = new Person();
driver.setCar(car2);
driver.drive();
}
2.3、接口传递依赖
public interface IDriver {
//接口传递依赖
public void drive(ICar car);
}
public interface ICar {
public void run();
}
public class BMW implements ICar {
@Override
public void run() {
System.out.println("开着宝马上路!");
}
}
public class Ferrari implements ICar {
@Override
public void run() {
System.out.println("开着法拉利上路!");
}
}
public class Person implements IDriver {
//接口传递依赖
@Override
public void drive(ICar car){
System.out.println("小明开始开车。。。");
car.run();
}
}
public class Client {
public static void main(String[] args) {
//接口传递依赖
IDriver driver = new Person();
driver.drive(new BMW());
driver.drive(new Ferrari());
}
}
三、总结
在实现依赖倒转原则时,我们需要针对抽象层编程,而将具体类的对象通过依赖注入(DependencyInjection, DI)的方式注入到其他对象中,依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,分别是:构造注入,设值注入(Setter注入) 和接口注入。构造注入是指通过构造函数来传入具体类的对象,设值注入是指通过Setter方法来传入具体类的对象,而接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。
依赖倒转首先要求类之间具有联系,将其联系抽象成接口或者抽象类,这样降低类之间的依赖。从而产生抽象类或接口之间的依赖,将具体事物时间的依赖转化成抽象接口的依赖。
在实际编程中,我们一般需要做到如下3点:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
- 使用继承时遵循里氏代换原则。
依赖倒转原则的核心就是要我们面向接口编程,理解了面向接口编程,也就理解了依赖倒转。