概述
高層模塊不應依賴低層模塊,二者都應該依賴其抽象。而抽象不應依賴細節,細節應該依賴抽象。依賴倒置原則的中心思想其實就是面向接口編程。
相對于細節的多變性,抽象的東西會穩定的多,所以以抽象為基礎搭建的架構自然也會比以細節為基礎搭建的架構穩定的多。
使用接口或抽象類的目的是為了更好的制定規范,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。
相信有讀過spring framework源碼的同學應該對這一點深以為是,比如其核心接口之一的BeanFactory接口之下就繼承了AutowireCapableBeanFactory、HierarchicalBeanFactory、ListableBeanFactory接口,而這些接口又被多個抽象類有選擇實現,正是對依賴倒置原則的應用才使得spring framework 框架具有了極高的健壯性和擴展性。
三寸反骨
我們不妨從相反的角度出發,寫個反例看看會有什么問題,今日反骨頗重,就是不想遵循依賴倒置原則。
比如我們現在需要編寫一個簡單的Person類,讓他可以接受郵件消息即可。
反例
public class DependecyInversion {public static void main(String[] args) {Person person = new Person();person.receive(new Email());}
}class Email{public String getInfo(){return "電子郵件信息: Hello,Email";}
}class Person {public void receive(Email email){System.out.println(email.getInfo());}
}
編程的過程十分愉快,如此簡單的需求甚至不需要聰明的我們多思考一秒。運行效果也完全滿足預期。
但是,來了一個不好不壞的消息:“能力需要擴展,客戶要求,除了收到email的能力外,微信消息也想收到!”
“反骨仔想了想,不要緊,我新增類,同時Person類也要增加相應的方法就好了呀”!
于是他加班1小時完成了這個需求,自信滿滿的走了。
但是,第二天又來了個不好不壞的消息:“客戶對產品很滿意,同時要求除了想收到email、微信消息之外,還想擴展幾十個軟件的消息,清單包含:“QQ、微博、墨跡天氣、釘釘…”
反骨仔爆炸了,因為這個需求如果繼續按他的思路去實現,類也爆炸了。實現的方法更是爆炸到難以維護。
所以說一定要設計先行,一個優秀的設計可能會占據相當長的開發時間,但是會為后期的擴展和維護提供強有力的保障!
優化設計
我們把時間推回兩天前,接到需求的我們首先便進行了深度剖析并達成了共識:“依賴倒置原則必須遵守!”于是,有了如下代碼:
public class DependecyInversion {public static void main(String[] args) {Person person = new Person();person.receive(new Email());person.receive(new WeChat());}
}
interface IReceive{public String getInfo();
}class Email implements IReceive{public String getInfo(){return "電子郵件信息: Hello,Email";}
}class WeChat implements IReceive{public String getInfo(){return "微信信息: Hello,WeChat";}
}class Person {public void receive(IReceive receiver){System.out.println(receiver.getInfo());}
}
當客戶說我要繼續擴展幾十個消息入口時,我們會發現,我們對于person類不需要做任何改動了,不過是需要遵照IReceive接口規范去實現新擴展的業務細節就可以了。善莫大焉。
依賴關系傳遞三板斧
常見的依賴關系傳遞有三種方式:
接口傳遞
public class DependencyPass {public static void main(String[] args) {ChangHong changHongTv = new ChangHong();OpenAndClose openAndClose = new OpenAndClose();openAndClose.open(changHongTv);}
}class ChangHong implements ITV,ITV2,ITV3{@Overridepublic void play() {System.out.println("長虹電視機打開了。");}
}
interface IOpenAndClose{public void open(ITV tv);
}
interface ITV{public void play();
}
//實現接口
class OpenAndClose implements IOpenAndClose{@Overridepublic void open(ITV tv) {tv.play();}
}
構造方法傳遞
public class DependencyPass {public static void main(String[] args) {ChangHong changHongTv = new ChangHong();OpenAndClose2 openAndClose2 = new OpenAndClose2(changHongTv);openAndClose2.open();}}
class ChangHong implements ITV,ITV2,ITV3{@Overridepublic void play() {System.out.println("長虹電視機打開了。");}
}
interface IOpenAndClose2{public void open();
}
interface ITV2{public void play();
}
class OpenAndClose2 implements IOpenAndClose2{public ITV2 tv;public OpenAndClose2(ITV2 tv){this.tv = tv;}@Overridepublic void open() {this.tv.play();}
}
setter方法傳遞
public class DependencyPass {public static void main(String[] args) {ChangHong changHongTv = new ChangHong();OpenAndClose3 openAndClose3 = new OpenAndClose3();openAndClose3.setTv(changHongTv);openAndClose3.open();}}class ChangHong implements ITV,ITV2,ITV3{@Overridepublic void play() {System.out.println("長虹電視機打開了。");}
}
interface IOpenAndClose3{public void open();public void setTv(ITV3 itv3);
}
interface ITV3{public void play();
}
class OpenAndClose3 implements IOpenAndClose3{private ITV3 itv3;@Overridepublic void setTv(ITV3 itv3) {this.itv3 = itv3;}@Overridepublic void open() {this.itv3.play();}
}
結
- 底層模塊盡量都要有抽象類或接口,或者兩者都有,程序穩定性更好;
- 變量的聲明類型盡量是抽象類或接口,這樣我們的變量引用和實際對象間就存在一個緩沖層,利于程序擴展和優化;
- 繼承時遵循里氏替換原則;
- 什么是里氏替換原則?下次講!
關注我,共同進步,每周至少一更。——Wayne