mp.weixin.qq.com/s/IxldxhPyH…
編程
以前咱們看了單一職責原則和開閉原則,今天咱們再來看裏式替換原則和依賴倒置原則,千萬別小看這些設計原則,他在設計模式中會有不少體現,因此理解好設計原則以後,那麼設計模式,也會讓你更加的好理解一點。設計模式
在面向對象的軟件設計中,只有儘可能下降各個模塊之間的耦合度,才能提升代碼的複用率,系統的可維護性、可擴展性才能提升。面向對象的軟件設計中,有23種經典的設計模式,是一套前人代碼設計經驗的總結,若是把設計模式比做武功招式,那麼設計原則就比如是內功心法。經常使用的設計原則有七個,下文將具體介紹。bash
單一職責原則:專一下降類的複雜度,實現類要職責單一;ide
開放關閉原則:全部面向對象原則的核心,設計要對擴展開發,對修改關閉;ui
裏式替換原則:實現開放關閉原則的重要方式之一,設計不要破壞繼承關係;spa
依賴倒置原則:系統抽象化的具體實現,要求面向接口編程,是面向對象設計的主要實現機制之一;設計
接口隔離原則:要求接口的方法儘可能少,接口儘可能細化;3d
迪米特法則:下降系統的耦合度,使一個模塊的修改儘可能少的影響其餘模塊,擴展會相對容易;code
組合複用原則:在軟件設計中,儘可能使用組合/聚合而不是繼承達到代碼複用的目的。orm
這些設計原則並不說咱們必定要遵循他們來進行設計,而是根據咱們的實際狀況去怎麼去選擇使用他們,來讓咱們的程序作的更加的完善。
定義
:
若是對每個類型爲T1的對象o1,都有類型爲T2的對象o2,使得以T1定義的全部程序P在全部的對象o1都代換成o2 時,程序P的行爲沒有發生變化,那麼類型 T2 是類型 T1 的子類型。
換句話來講,一個軟件實體若是使用一個基類的話,那麼必定適用於其子類,並且它根本不會察覺出基類對象和子類對象的區別。
好比說,假設有兩個類,一個是Base類,另外一個是Derived類,而且Derived類是Base的子類,那麼一個方法若是能夠接受一個基類對象b的話:method(Base b)
,那麼它必然能夠接受一個子類對象d,能夠有 method1(d)
裏式替換原則是繼承複用的基石,只有當衍生類能夠替換掉基類,軟件單位的功能不會受到影響的時候,基類才能真正被複用,而衍生類也纔可以在基類的基礎上增長新的行爲。
咱們經過一個例子來理解一下:
《西遊記》中,美猴王下地府橋段,個位應該有印象把,到達閻王殿以後,拿到生死簿,把生死簿上全部的包括本身,還有其餘的獼猴,全部的猴子猴算都給劃了,這也是致使以後真假美猴王橋段的前序。
畫個圖理解
很顯然,地府管理一切生靈的生死的方法都是經過類來進行區分的,好比孫悟空就是石猴,以後出現的那個六耳獼猴就是獼猴,可是他們都是屬於同一個類,猴類,就像下圖中。
所以,孫悟空把猴類中有姓名的都從生死簿勾掉以後,顯然是由於勾魂小鬼們並不區分石猴類與獼猴類,就像下圖:
換句話來講,只要是猴類適用的,獼猴和石猴都適用,這其實就是裏式替換原則。
這是第一種解釋,還有第二個更加通俗易懂的解釋:全部引用基類的地方必須能透明地使用其子類的對象。
第二種定義比較通俗,容易理解:只要有父類出現的地方,均可以用子類來替代,並且不會出現任何錯誤和異常。可是反過來則不行,有子類出現的地方,不能用其父類替代。
實例代碼
:
public class TestA { public void fun(int a,int b){ System.out.println(a+"+"+b+"="+(a+b)); } public static void main(String[] args) { System.out.println("父類的運行結果"); TestA a=new TestA(); a.fun(1,2); //父類存在的地方,能夠用子類替代 //子類B替代父類A System.out.println("子類替代父類後的運行結果"); TestB b=new TestB(); b.fun(1,2); }}class TestB extends TestA{ @Override public void fun(int a, int b) { System.out.println(a+"-"+b+"="+(a-b)); }}複製代碼
你們確定也都能猜出來結果是什麼樣子的
父類的運行結果1+2=3子類替代父類後的運行結果1-2=-1Process finished with exit code 0複製代碼
咱們想要的結果是「1+2=3」。能夠看到,方法重寫後結果就不是了咱們想要的結果了,也就是這個程序中子類B不能替代父類A。這違反了里氏替換原則原則,從而給程序形成了錯誤。
子類中能夠增長本身特有的方法
這個很容易理解,子類繼承了父類,擁有了父類和方法,同時還能夠定義本身有,而父類沒有的方法。這是在繼承父類方法的基礎上進行功能的擴展,符合里氏替換原則。
public class TestA { public void fun(int a,int b){ System.out.println(a+"+"+b+"="+(a+b)); } public static void main(String[] args) { System.out.println("父類的運行結果"); TestA a=new TestA(); a.fun(1,2); //父類存在的地方,能夠用子類替代 //子類B替代父類A System.out.println("子類替代父類後的運行結果"); TestB b=new TestB(); b.fun(1,2); b.newFun(); }}class TestB extends TestA{ public void newFun(){ System.out.println("這是子類的新方法..."); }}複製代碼
此次運行出來的代碼結果就是咱們意料中的內容了
父類的運行結果1+2=3子類替代父類後的運行結果1+2=3這是子類的新方法...Process finished with exit code 0複製代碼
AVA語言對裏式替換原則支持的侷限
:
JAVA編譯器的檢查是有侷限性的,爲何呢?舉個例子來講,描述一個物體大小的量有精度和準確度兩種屬性。所謂的精度,就是這個量的有效數字有多少位;而所謂的精準度,是這個量與真實的物體大小相符合到什麼程度。
一個量能夠有很高的精度,可是卻沒法與真實物體的狀況相吻合,JAVA語言編譯器可以檢查的,僅僅是至關於精度的屬性而已,它沒有辦法去檢查這個量與真實物體的差距。
換一句話來講,JAVA編譯器不能檢查一個系統在實現和商業邏輯上是否知足裏式替換原則。
而裏式替換原則在設計模式中也有體現,請關注咱們的知識星球,連接在文末,咱們將每週更新一篇關於設計模式的文章。
若是說實現開閉原則的關鍵事抽象化,是面向對象設計的目標的話,依賴倒置原則就是這個面向對象設計的主要機制。
定義
:
抽象不該該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而不是針對實現編程。
爲何要實現倒置?這也是咱們看這個定義的時候產生的一些問題,那麼咱們就來講說。
簡單的來講,傳統的過程性系統的設計辦法傾向於使高層次的模塊依賴於低層次的模塊,抽象層依賴於具體層次,倒置原則是要把這個錯誤的依賴關係倒轉過來,這就是依賴倒置原則的由來。也是爲何要進行依賴倒置。
依賴倒置原則的實現方法
依賴倒置原則的目的是經過要面向接口的編程來下降類間的耦合性,因此咱們在實際編程中只要遵循如下4點,就能在項目中知足這個規則:
每一個類儘可能提供接口或抽象類,或者二者都具有。
變量的聲明類型儘可能是接口或者是抽象類。
任何類都不該該從具體類派生。
使用繼承時儘可能遵循里氏替換原則。
下面咱們經過一些代碼實例(商品售賣)來進行理解:
class BeijingShop implements Shop{ public String sell(){ return "北京商店售賣:北京烤鴨,稻香村月餅"; } } class ShanDongShop implements Shop{ @Override public String sell() { return "山東商店售賣:德州扒雞,煙臺蘋果"; } } //若是說顧客去購買商品class Customer{ public void shopping(ShanDongShop shop){ //購物 System.out.println(shop.sell()); }}//這是在山東商店購買,若是說是在北京商店購買就會這樣class Customer{ public void shopping(BeijingShop shop) { //購物 System.out.println(shop.sell()); }}複製代碼
這也是這種設計的存在缺陷,顧客每更換一家商店,都要修改一次代碼,這明顯違背了開閉原則。存在以上缺點的緣由是:顧客類設計時同具體的商店類綁定了,這違背了依賴倒置原則。解決方式咱們能夠定義一個共同的接口Shop,就能夠這樣了。
public class TestSale { public static void main(String[] args) { Customer c = new Customer(); System.out.println("---顧客購買商品以下---"); c.shopping(new ShanDongShop()); c.shopping(new BeijingShop()); }}interface Shop{ //售賣方法 public String sell();}class BeijingShop implements Shop{ public String sell(){ return "北京商店售賣:北京烤鴨,稻香村月餅"; }}class ShanDongShop implements Shop{ @Override public String sell() { return "山東商店售賣:德州扒雞,煙臺蘋果"; }}class Customer{ public void shopping(Shop shop) { System.out.println(shop.sell());//購物 }}複製代碼
程序運行結果
---顧客購買商品以下---山東商店售賣:德州扒雞,煙臺蘋果北京商店售賣:北京烤鴨,稻香村月餅Process finished with exit code 0複製代碼
這樣,無論顧客類 Customer 訪問什麼商店,或者增長新的商店,都不須要修改原有代碼了。
依賴倒置原則是OO設計的核心原則,設計模式的研究和應用是以依賴致使原則爲知道原則的,在知識星球中的設計模式中咱們將會一一給你們體現。
我是懿,一個正在被打擊還在努力前進的碼農。歡迎你們關注咱們的公衆號,加入咱們的知識星球,咱們在知識星球中等着你的加入。