依賴倒置原則
定義
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻譯上面的話有一下三層含義:
- 高層模塊不應(yīng)該依賴底層模塊,兩者都應(yīng)該依賴其抽象
- 抽象不應(yīng)該依賴其細(xì)節(jié)
- 細(xì)節(jié)應(yīng)該依賴抽象
高層模塊和低層模塊容易理解,每一個(gè)邏輯的實(shí)現(xiàn)都是由原子邏輯組成的,不可分割的原子邏輯就是低層模塊,原子邏輯的再組裝就是高層模塊。那什么是抽象?什么又是細(xì)節(jié)呢?在Java語(yǔ)言中,==抽象就是指接口或抽象類==,兩者都是不能直接被實(shí)例化的;==細(xì)節(jié)就是實(shí)現(xiàn)類==,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點(diǎn)就是可以直接被實(shí)例化,也就是可以加上一個(gè)關(guān)鍵字new產(chǎn)生一個(gè)對(duì)象。依賴倒置原則在Java語(yǔ)言中的表現(xiàn)就是:
- 模塊間的依賴通過(guò)抽象發(fā)生,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過(guò)接口或抽象類產(chǎn)生的;
- 接口或抽象類不依賴于實(shí)現(xiàn)類;
- 實(shí)現(xiàn)類依賴接口或抽象類。
更加精簡(jiǎn)的定義就是“面向接口編程”——OOD(Object-Oriented Design,面向?qū)ο笤O(shè)計(jì))的精髓之一。
依賴倒置原則優(yōu)點(diǎn)
采用依賴倒置原則可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性,降低并行開(kāi)發(fā)引起的風(fēng)險(xiǎn),提高代碼的可讀性和可維護(hù)性。

下面是源代碼
class Benz {
public void run(){
System.out.println("奔馳汽車開(kāi)始運(yùn)行...");
}
}
class Driver {
public void drive(Benz benz){
benz.run();
}
}
public class Client {
public static void main(String args[]){
Benz benz = new Benz();
Driver zs = new Driver();
zs.drive(benz);
}
}
分析
通過(guò)上面的代碼,知道如果需要司機(jī)重新開(kāi)別的車,顯然需要改司機(jī)類,汽車類,這樣的開(kāi)發(fā)成本是非常高的,同時(shí)也違背了依賴倒置的原則,下面引入依賴倒置原則重新設(shè)計(jì)開(kāi)汽車的方法:

抽象是對(duì)實(shí)現(xiàn)的約束,對(duì)依賴者而言,也是一種契約,不僅僅約束自己,還同時(shí)約束自己與外部的關(guān)系,其目的是保證所有的細(xì)節(jié)不脫離契約的范疇,確保約束雙方按照既定的契約(抽象)共同發(fā)展,只要抽象這根基線在,細(xì)節(jié)就脫離不了這個(gè)圈圈,始終讓你的對(duì)象做到“言必信,行必果”。
依賴的三種寫法
1、構(gòu)造函數(shù)注入
在類中通過(guò)構(gòu)造函數(shù)聲明依賴對(duì)象,按照依賴注入的說(shuō)法,這種方式叫做構(gòu)造函數(shù)注入。
按照這種方式的注入,IDriver和Driver的程序如下所示:
public interface IDriver {
public void drive();
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開(kāi)始運(yùn)行...");
}
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Client {
public static void main(String args[]){
ICar car = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive(car);
zs.drive();
zs = new Drive(bmw);
zs.drive();
}
}
2、set注入
set注入其實(shí)就是set方法注入
如下所示:
public interface IDriver {
// 注入汽車接口
public void setCar(ICar car);
public void drive();
}
public class Drive implements IDriver {
private ICar car;
// 通過(guò)set注入汽車接口
public void setCar(ICar car) {
this.car = car;
}
// 司機(jī)不用關(guān)心自己駕駛的是什么汽車
public void drive() {
this.car.run();
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開(kāi)始運(yùn)行...");
}
}
public class Drive implements IDriver {
private ICar car;
public Drive(ICar car){
this.car = car;
}
public void drive() {
this.car.run();
}
}
public class Client {
public static void main(String args[]){
ICar benz = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive();
zs.setCar(benz);
zs.drive();
zs.setCar(bmw);
zs.drive();
}
}
3、接口聲明依賴對(duì)象
剛才的分析就是接口聲明依賴對(duì)象,代碼如下所示:
public interface IDriver {
public void drive(ICar car);
}
public class Drive implements IDriver {
@Override
public void drive(ICar car) {
car.run();
}
}
public interface ICar {
void run();
}
public class BMW implements ICar{
@Override
public void run() {
System.out.println("寶馬汽車開(kāi)始運(yùn)行...");
}
}
public class Benz implements ICar{
public void run(){
System.out.println("奔馳汽車開(kāi)始運(yùn)行...");
}
}
public class Client {
public static void main(String args[]){
ICar benz = new Benz();
ICar bmw = new BMW();
IDriver zs = new Drive();
zs.drive(benz);
zs.drive(bmw);
}
}
總結(jié)
依賴倒置原則的本質(zhì)就是通過(guò)抽象(接口或抽象類)使各個(gè)類或模塊的實(shí)現(xiàn)彼此獨(dú)立,不互相影響,實(shí)現(xiàn)模塊間的松耦合
所以我們需要遵循以下幾點(diǎn):
- 每個(gè)類盡量都有接口或抽象類,或者抽象類和接口兩者都具備
這是依賴倒置的基本要求,接口和抽象類都是屬于抽象的,有了抽象才可能依賴倒置。 - 變量的表面類型盡量是接口或者是抽象類
很多書上說(shuō)變量的類型一定要是接口或者是抽象類,這個(gè)有點(diǎn)絕對(duì)化了,比如一個(gè)工具類,xxxUtils一般是不需要接口或是抽象類的。還有,如果你要使用類的clone方法,就必須使用實(shí)現(xiàn)類,這個(gè)是JDK提供的一個(gè)規(guī)范。 - 任何類都不應(yīng)該從具體類派生
如果一個(gè)項(xiàng)目處于開(kāi)發(fā)狀態(tài),確實(shí)不應(yīng)該有從具體類派生出子類的情況,但這也不是絕對(duì)的,因?yàn)槿硕际菚?huì)犯錯(cuò)誤的,有時(shí)設(shè)計(jì)缺陷是在所難免的,因此只要不超過(guò)兩層的繼承都是可以忍受的。特別是負(fù)責(zé)項(xiàng)目維護(hù)的同志,基本上可以不考慮這個(gè)規(guī)則,為什么?維護(hù)工作基本上都是進(jìn)行擴(kuò)展開(kāi)發(fā),修復(fù)行為,通過(guò)一個(gè)繼承關(guān)系,覆寫一個(gè)方法就可以修正一個(gè)很大的Bug,何必去繼承最高的基類呢?(當(dāng)然這種情況盡量發(fā)生在不甚了解父類或者無(wú)法獲得父類代碼的情況下。) - 盡量不要覆寫基類的方法
如果基類是一個(gè)抽象類,而且這個(gè)方法已經(jīng)實(shí)現(xiàn)了,子類盡量不要覆寫。類間依賴的是抽象,覆寫了抽象方法,對(duì)依賴的穩(wěn)定性會(huì)產(chǎn)生一定的影響。 - 結(jié)合里氏替換原則使用