很多初學(xué)編程的小伙伴在編程時會發(fā)現(xiàn)兴猩,自己寫的類總是頻繁的用到(依賴)其他類鞭衩,一旦被依賴的類需要修改敲茄,那么其他的類也統(tǒng)統(tǒng)都要修改一遍,讓人感覺煩不勝煩副签。若是小型的程序也緊緊是覺得煩而已遥椿,可一旦是大型的工程,這種強(qiáng)耦合的程序一旦有某一個細(xì)節(jié)放生改變淆储,那是砸電腦的心都有修壕。
各個具體類之間發(fā)生了直接的依賴關(guān)系,使得這些類緊緊地耦合在了一起遏考,從而降低了程序的穩(wěn)定性、可維護(hù)性和可讀性蓝谨。要解決這個問題灌具,我們可以用一些方法來將這些程序解耦,降低其耦合性譬巫。這種方法就是我即將要講到的依賴倒置原則(Dependence Inversion Principle ,DIP)咖楣。
定義
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)該依賴抽象。
也就是說咕缎,高層模塊珠十、低層模塊、細(xì)節(jié)都應(yīng)該依賴抽象凭豪。
- 低層模塊:組成一個大邏輯的顆粒原子邏輯焙蹭。
- 高層模塊:顆粒原子邏輯組成的模塊。
- 抽象:指接口或抽象類嫂伞,兩者都不能被實例化孔厉。
- 細(xì)節(jié):實現(xiàn)類拯钻,實現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié),其特點就是可以直接被實例化撰豺。
java的精髓
依賴倒置原則在java中的三種含義:
- 模塊間的依賴通過抽象發(fā)生粪般,實現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的污桦;
- 接口或抽象類不依賴于實現(xiàn)類亩歹;
- 實現(xiàn)類依賴接口或抽象類。
這三種含義在java的精髓之一——面向接口編程中體現(xiàn)得淋漓盡致寡润。
用與不用依賴倒置原則的對比
既然一開始就說到不用依賴倒置原則有多糟糕捆憎,那么下面我們就用一個簡單的程序來證明一下。
不使用依賴倒置原則:
程序1
/**
* 大眾汽車類
* @author 葉漢偉
*/
public class DaZhong {
public void run(){
System.out.println("開大眾汽車");
}
}
/**
* 司機(jī)類
* @author 葉漢偉
*/
public class Driver {
public void drive(DaZhong daZhong){
daZhong.run();
}
}
public class Client {
public static void main(String[] args){
Driver Tom=new Driver();
DaZhong daZhong=new DaZhong();
Tom.drive(daZhong);
}
}
看上去沒什么嘛梭纹,這不是好好的嗎躲惰?好像也是哦。那么既然司機(jī)會開大眾汽車变抽,那應(yīng)該會開寶馬吧础拨。我們讓它開一下寶馬試試。
先生產(chǎn)一輛寶馬給他:
程序2
/**
* 寶馬車類
* @author 葉漢偉
*/
public class BaoMa {
public void run(){
System.out.println("開寶馬車");
}
}
當(dāng)我們要讓司機(jī)開寶馬的時候绍载,他確開不了诡宗,程序報錯了。感情他考的是大眾駕照击儡,有寶馬都開不了啊塔沃。
其實要讓司機(jī)開寶馬車也容易,把司機(jī)類開車方法的參數(shù)改一下就成了唄阳谍。的確可以蛀柴。但又想想,現(xiàn)在知識多了個寶馬車矫夯,如果我再多個奔馳鸽疾、本田什么的,那不就是要經(jīng)常改训貌,大改特改制肮?對于大型的項目來說,這是致命的递沪。
使用依賴倒置原則
那么接下來豺鼻,我們看一下使用依賴倒置原則有什么優(yōu)勢。
程序3
/**
* 車子接口
* @author 葉漢偉
*/
public interface ICar {
public void run();
}
/**
* 大眾汽車類
* @author 葉漢偉
*/
public class DaZhong implements ICar{
public void run(){
System.out.println("開大眾汽車");
}
}
/**
* 寶馬車類
* @author 葉漢偉
*/
public class BaoMa implements ICar{
public void run(){
System.out.println("開寶馬車");
}
}
/**
* 司機(jī)接口
* @author 葉漢偉
*/
public interface IDriver {
public void drive(ICar car);
}
/**
* 司機(jī)類
* @author 葉漢偉
*/
public class Driver implements IDriver{
public void drive(ICar car){
car.run();
}
}
public class Client {
public static void main(String[] args){
IDriver Tom=new Driver();
//Tom開大眾汽車
ICar daZhong=new DaZhong();
Tom.drive(daZhong);
//Tom開寶馬
ICar baoMa=new BaoMa();
Tom.drive(baoMa);
}
}
看款慨,現(xiàn)在不僅可以開大眾拘领,而且可以開寶馬了。要還有什么本田樱调、奔馳汽車约素,一個drive方法都能開届良,而開其他的車只需要修改一下客戶端就可以了。
上面的程序在實現(xiàn)類之間不發(fā)生依賴關(guān)系圣猎,他們的依賴關(guān)系是在接口處發(fā)生的士葫,這樣就可以大大的降低了類之間的耦合。其實這里不僅用到了依賴倒置原則送悔,還用到了里氏替換原則慢显,從類Client中可以看出來。
實現(xiàn)依賴的三種方法
對象的依賴關(guān)系可以通過三種方法來實現(xiàn):
- 接口聲明依賴對象
- 構(gòu)造函數(shù)傳遞依賴對象
- setter方法傳遞依賴對象
接口聲明依賴對象
在接口處就聲明了依賴的對象欠啤。如程序3中的司機(jī)接口IDriver荚藻,其方法drive()的形參是ICar類型的。那么我們可以說IDrive與ICar放生了依賴關(guān)系洁段,這個依賴對象在接口處已經(jīng)聲明了应狱。接口聲明依賴的方法也叫接口注入。
構(gòu)造函數(shù)傳遞依賴對象
在類中通過構(gòu)造函數(shù)聲明依賴對象祠丝。具體實現(xiàn)如下:
程序4
/**
* 司機(jī)接口
* @author 葉漢偉
*/
public interface IDriver {
public void drive();
}
/**
* 司機(jī)類
* @author 葉漢偉
*/
public class Driver implements IDriver{
private ICar car;
//通過構(gòu)造函數(shù)注入依賴對象
public Driver(ICar car){
this.car=car;
}
public void drive(){
this.car.run();
}
}
如果我們想要司機(jī)開寶馬車疾呻,只需要將寶馬車對象傳入構(gòu)造函數(shù)即可。這種方法又叫做構(gòu)造函數(shù)注入写半。
setter方法傳遞依賴對象
這種方法通過在抽象中增加一個setter方法實現(xiàn)岸蜗。具體實現(xiàn)如下:
程序5
/**
* 司機(jī)接口
* @author 葉漢偉
*/
public interface IDriver {
public void setCar(ICar car);
public void drive();
}
/**
* 司機(jī)類
* @author 葉漢偉
*/
public class Driver implements IDriver{
private ICar car;
//setter方法傳遞依賴對象
public void setCar(ICar car){
this.car=car;
}
public void drive(){
this.car.run();
}
}
若要司機(jī)開那種車,只需要將車子的對象通過Driver對象的setCar()方法傳入即可叠蝇。這種方法又叫setter依賴注入璃岳。
總結(jié)
通過依賴倒置原則,我們可以實現(xiàn)模塊中的松耦合悔捶。具體來說總結(jié)為一下幾點規(guī)則:
- 每個類盡量都有接口或抽象類矾睦,或者抽象類和接口兩者都具備
- 變量的表面類型盡量是接口或者是抽象類
- 任何類都不應(yīng)該從具體類派生
- 盡量不要覆寫基類的方法
- 結(jié)合里氏替換原則使用
還沒有形成面向接口編程的java學(xué)習(xí)者們,趕緊將這個規(guī)則用起來吧炎功,用過一段之間,你會感覺水平飆升缓溅,在代碼間游走更加柔韌有余蛇损。