面向對象六大原則----里氏替換原則,依賴倒置原則

Java 中面向對象編程六大原則:

單一職責原則 英文名稱是Single Responsibility Principle,簡稱SRP

開閉原則 英文全稱是Open Close Principle,簡稱OCP

里氏替換原則 英文全稱是Liskov Substitution Principle,簡稱LSP

依賴倒置原則  英文全稱是Dependence Inversion Principle,簡稱DIP

接口隔離原則 英文全稱是InterfaceSegregation Principles,簡稱ISP

迪米特原則 英文全稱爲Law of Demeter,簡稱LOD,也稱爲最少知識原則(Least Knowledge Principle)


構建擴展性更好的系統——里氏替換原則

前面兩章講了單一職責原則開閉原則。里氏替換原則英文全稱是Liskov Substitution Principle,簡稱LSP。 由2008年圖靈獎得主、美國第一位計算機科學女博士Barbara Liskov教授和卡內基·梅隆大學Jeannette Wing教授於1994年提出。其嚴格表述如下:如果對每一個類型爲S的對象o1,都有類型爲T的對象o2,使得以T定義的所有程序P在所有的對象o1代換o2時,程序P的行爲沒有變化,那麼類型S是類型T的子類型。這個定義比較拗口且難以理解,因此我們一般使用它的另一個通俗版定義::所有引用基類的地方必須能透明地使用其子類的對象。

 里氏代換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那麼它不一定能夠使用基類對象。例如:我喜歡動物,那我一定喜歡狗,因爲狗是動物的子類;但是我喜歡狗,不能據此斷定我喜歡動物,因爲我並不喜歡老鼠,雖然它也是動物。

繼續在前面兩章的ImageLoader爲例:


上圖MemoryCache、DiskCache、DoubleCache都實現了ImageCahe接口,ImageLoader類需要一個ImageCahe類,而上面的三種具體實現Cache類都可以替換ImageCache的工作,並且能夠保證行爲的正確性。所以ImageCache建立了獲取緩存圖片、保存緩存圖片的接口規範,MemoryCache等根據接口規範實現了相應的功能,用戶只需要在使用時指定具體的緩存對象就可以動態地替換ImageLoader中的緩存策略。這就使得ImageLoader的緩存系統具有了無限的可能性,也就是保證了可擴展性。

如果當ImageLoader中的setImageCache(ImageCache cache)中的cache對象不能夠被子類所替換,那麼用戶就無法設置不同的緩存對象以及用戶也無法自定義自己的緩存實現。里氏替換原則就爲這類問題提供了指導原則,也就是建立抽象,通過抽象建立規範,具體的實現在運行時替換掉抽象,保證系統的高擴展性、靈活性。開閉原則和里氏替換原則往往是生死相依、不棄不離的,通過里氏替換來達到對擴展開放,對修改關閉的效果。所以,這兩個原則都同時強調了一個OOP的重要特性——抽象,因此,在開發過程中運用抽象是走向代碼優化的重要一步。

  在使用里氏代換原則時需要注意如下幾個問題:

      (1)子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法。根據里氏代換原則,爲了保證系統的擴展性,在程序中通常使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應的聲明,則無法在以父類定義的對象中使用該方法。

      (2)  我們在運用里氏代換原則時,儘量把父類設計爲抽象類或者接口,讓子類繼承父類或實現父接口,並實現在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現。里氏代換原則是開閉原則的具體實現手段之一。


 讓項目擁有變化的能力——依賴倒置原則

依賴倒置原則英文全稱是Dependence Inversion Principle,簡稱DIP。抽象不應該依賴於細節,細節應當依賴於抽象。換言之,要針對接口編程,而不是針對實現編程。賴倒置原則指代了一種特定的解耦形式,使得高層次的模塊不依賴於低層次的模塊的實現細節的目的,依賴模塊被顛倒了。這個概念有點不好理解,這到底是什麼意思呢? 
依賴倒置原則的幾個關鍵點:

  • 高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象;
  • 抽象不應該依賴細節;
  • 細節應該依賴抽象。

在Java語言中,抽象就是指接口或抽象類,兩者都是不能直接被實例化的;細節就是實現類,實現接口或繼承抽象類而產生的類就是細節,其特點就是,可以直接被實例化,也就是可以加上一個關鍵字 new 產生一個對象。高層模塊就是調用端,低層模塊就是具體實現類。依賴倒置原則在 Java 語言中的表現就是:模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是通過接口或抽象類產生的。這又是一個將理論抽象化的實例,其實一句話就可以概括:面向接口編程,或者說是面向抽象編程,這裏的抽象指的是接口或者抽象類。面向接口編程是面向對象精髓之一。

如果在類與類直接依賴於細節,那麼它們之間就有直接的耦合,當具體實現需要變化時,意味着在這要同時修改依賴者的代碼,並且限制了系統的可擴展性。在講解單一原則時,開始ImageLoader代碼如下:

public class ImageLoader {
    // 內存緩存 ( 直接依賴於細節 )
    MemoryCache mMemoryCache = new MemoryCache();
     // 加載圖片到ImageView中
    public void displayImage(String url, ImageView imageView) {
       Bitmap bmp = mMemoryCache.get(url);
        if (bmp == null) {
            downloadImage(url, imageView);
        } else {
            imageView.setImageBitmap(bmp);
        }
    }

    public void setImageCache(MemoryCache cache) {
        mCache = cache ;
    }
    // 代碼省略
}

ImageLoader直接依賴於MemoryCache,這個MemoryCache是一個具體實現,而不是一個抽象類或者接口。這導致了ImageLoader直接依賴了具體細節,當MemoryCache不能滿足ImageLoader而需要被其他緩存實現替換時,此時就必須修改ImageLoader的代碼。而後面引入ImageCahe這個接口後,不管怎麼修改MemoryCache類,都不用修改其依賴於ImageCahe類的ImageLoader類,最後的代碼如下:

public interface ImageCache{
   void put(String url,Bitmap bitmap);
   Bitmap get(String url);
}

public class ImageLoader {
    // 圖片緩存
    ImageCache mImageCache = null;
    
    public void setImageCache(ImageCache cache){
        mImageCache = cache;
    }
   
    public  void displayImage(final String url, final ImageView imageView) {
        if(mImageCache != null){
            Bitmap bitmap = mImageCache.get(url);
            if(bitmap != null){
                imageView.setImageBitmap(bitmap);
                return;
            }
        }
        downLoadImage()
     }
}

所以依賴倒轉原則要求我們在程序代碼中傳遞參數時或在關聯關係中,儘量引用層次高的抽象層類,即使用接口和抽象類進行變量類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不要用具體類來做這些事情。爲了確保該原則的應用,一個具體類應當只實現接口或抽象類中聲明過的方法,而不要給出多餘的方法,否則將無法調用到在子類中增加的新方法。


代碼github地址:點擊打開鏈接

更多精彩Android技術可以關注我們的微信公衆號,掃一掃下方的二維碼或搜索關注公共號: Android老鳥

                                                

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章