【設計模式筆記】(零)- 物件導向的六大原則

weixin_33912445發表於2018-03-11
595349-6dd228cf6cee0592.png

1.單一職責原則(Single Responsibility Principle,縮寫SRP)

單一職責原則,就一個類而言,應該只有一個引起它變化的原因。簡單說,一個類應該是一組高度相關的函式、資料的封裝;也就是高內聚。

下面程式碼為 ImageLoader(圖片載入)類的程式碼

public class ImageLoader{
    //圖片快取
    LruCache<String,Bitmap> mImageCache;
    //執行緒池,執行緒數量為CPU的數量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProessors());
    
    public ImageLoader(){
        initImageCache();
    }
    
    private void initImageCache() {
        //省略...         
   }
    
   //顯示圖片
   public  void displayImage(final String url, final ImageView imageView) {
        //省略... 
   }
    
   //下載圖片
   public  Bitmap downloadImage(String imageUrl) {
        //省略... 
        return bitmap;
   }
}

這裡可以看出來 ImageLoader 類作用有初始化圖片快取、顯示圖片、下載圖片,顯然顯示圖片和下載圖片兩個方法與初始化圖片快取方法相比作用就顯得有些不相關。也就是不符合單一職責原則。按照邏輯進行分拆之後得到ImageLoaderImageCache兩個類。ImageLoader負責圖片載入邏輯,ImageCache負責處理圖片快取邏輯,這樣職責就清楚了,當與快取相關的邏輯需要改變時,不需要修改ImageLoader類,而圖片載入的邏輯需要修改時也不會影響到快取處理邏輯。

595349-abe5de26e192247d
image

ImageLoader程式碼修改如下所示:

/** 圖片載入類 */
public  class ImageLoader {
    //圖片快取
    ImageCache mImageCache = new ImageCache() ;
    //執行緒池,執行緒數量為CPU的數量
    ExecutorService mExecutorService = Executors.newFixedThreadPool (Runtime.getRuntime().availableProcessors());

    //載入圖片
    public  void displayImage(final String url, final ImageView imageView) {
        //省略... 
     }

    public  Bitmap downloadImage(String imageUrl) {
        //省略... 
        return bitmap;
    }
} 

而新增的ImageCache類用於處理圖片快取,具體程式碼如下:

public class ImageCache {
    // 圖片LRU快取
    LruCache<String, Bitmap> mImageCache;

    public ImageCache() {
        initImageCache();
    }

    private void initImageCache() {
        //省略... 
    }

    public void put(String url, Bitmap bitmap) {
        mImageCache.put(url, bitmap) ;
    }

    public Bitmap get(String url) {
        return mImageCache.get(url) ;
    }
}

如何劃分一個類、一個函式的職責,每個人都有自己的看法,這需要根據個人經驗、具體的業務邏輯而定。

2.開閉原則(Open Close Principle,縮寫OCP)

開閉原則是Java中最基礎的設計原則,知道我們如何建立一個穩定的、靈活的系統。定義:軟體中得物件應該對於擴充套件是開放的,但是對於修改是封閉的。

595349-f88066af78b82bbd.png

例如圖中MemonyCacheDiskCacheDoubleCache都實現了ImageCache介面,ImageLoader使用ImageCache處理快取,就意味著ImageLoader可以通過setImageCache()指定使用哪一種快取型別,可以使三種快取其中任意一種,同時不需要修改ImageLoader中的程式碼。這也就是開閉原則的體現。

簡單地說,當軟體需要變化時,應該儘量通過擴充套件的方式來實現變化,而不是通過修改已有的程式碼來實現。“應該儘量”4個字說明OCP原則並不是說絕對不可以修改原始類的,當程式碼需要需要重構的時候要及時重構,使程式碼恢復正常,而不是通過繼承等方式新增新的實現,這會導致型別的膨脹以及歷史遺留程式碼的冗餘。

開發過程中都沒有那麼理想的狀況,因此,凡事也是需要結合具體情況再做決定,目的是更穩定、更靈活同時保有原有的正確性。

3.里氏替換原則(Liskov Substitution Principle,縮寫LSP)

里氏替換原則,書上原話的定義簡直看不得(解釋的辣眼睛,完全看不懂),簡單地說就是所有引用基類的地方必須能透明地使用其子類的物件。只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能適應。其實就是:抽象。

595349-b4ac20004a407717.png

上圖可以看出,Window依賴於View,而ButtonTextView繼承View。這裡任何繼承自View類的子類都可以設定給show()方法,也就是里氏替換原則。通過里氏替換,就可以自定義各式各樣的View,然後傳遞給Window,並且將View顯示到螢幕上。

里氏替換原則的核心原理是抽象,抽象又依賴於繼承這個特性,繼承的優缺點都相當明

優點:

  • 程式碼重用,減少建立類的成本,每個子類都擁有父類的方法和屬性
  • 子類與父類基本相似,但又與父類有所區別
  • 提高程式碼的可擴充套件性

缺點:

  • 繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法
  • 可能造成子類程式碼冗餘、靈活性降低,因為子類必須擁有父類的屬性和方法

事物總是具有兩面性,如何權衡利與弊都是需要根據具體場景來做出選擇並加以處理。

4.依賴倒置原則(Dependence Inversion Principle,縮寫DIP)

依賴倒置原則,說的就是一種特定的就形式,使得高層次的模組不依賴於低層次的模組的實現細節的目的,依賴模組被顛倒了。依賴倒置原則的幾個關鍵點:

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

是不是覺得和沒說一個樣,至少我是這麼覺得的;繼續往後看才明白,所謂高層模組就是呼叫端,低層模組就是具體實現類。依賴倒置原則在 Java 語言中的表現就是:模組間的依賴通過抽象發生,實現類之間不發生直接的依賴關係,通過介面抽象類產生依賴關係。什麼是依賴關係呢?其實就是相互之間的呼叫關係。概括來說就是面向介面變成,或者是面向抽象程式設計。

其實依賴倒置原則主要目的就是解耦

595349-f88066af78b82bbd.png

依然可以使用這張圖來表示,表達出來就是ImageLoaderMemonyCache等並沒有直接關係,甚至ImageLoader只需要實現ImageCache類或繼承其他已有的ImageCache子類完成相應的快取功能,然後將具體的實現注入到ImageLoader即可實現快取功能的替換。這也是依賴倒置原則的體現。

5.介面隔離原則(Interface Segregation Principle,縮寫ISP)

介面隔離原則將非常龐大、臃腫的介面拆分成為更小的和更具體的介面;目的就是解耦。這個原則的做法和單一職責原則有點相似,就是說介面中得方法保持更高的相關性、儘量少,避免掉不需要的方法。

舉個栗子,現在有一個帶有呼吸方法的介面,還有一個打鼾方法的介面;如果說,你把這兩個方法放到一個介面中,基本就是違背介面隔離原則,畢竟呼吸和打鼾沒有什麼緊密的相關性;不可能說我需要呼吸的時候一定需要打鼾吧!

6.迪米特原則(Law of Principle,縮寫LOP)

迪米特原則也稱為最少知識原則(Least Knowledge Principle),定義:一個物件應該對其他物件有最少的瞭解。通俗地講,一個類要對自己需要呼叫的類知道得最少,類的內部如何實現、如何複雜都與呼叫者(或依賴者)沒關係,呼叫者(或依賴者)只需要知道他需要的方法即可,其他的不需要關心。類與類之間的關係越密切,耦合度越大;當一個類發生改變時,對另一個類的影響也越大。

迪米特法則還有一個英文解釋是:Only talk to your immedate friends,翻譯過來就是:只與直接的朋友通訊。什麼叫做直接的朋友呢?每個物件都必然會與其他物件有耦合關係,兩個物件之間的耦合就成為朋友關係,這種關係的型別有很多,例如組合、聚合、依賴等。

下圖是Volley框架中的DiskBasedCache類(實現Cache介面)和Cache介面的程式碼

595349-4374dde8aee3b9e2.png
595349-d57b4afcc201010b.png

Volley中的Response快取介面的設計。Cache介面定義了快取類需要實現的最小介面,依賴快取類的物件只需要知道這些介面即可。例如快取的具體實現類DiskBasedCache,該快取類將Response序列化到本地,這就需要操作File以及相關的類。

Volley的直接朋友就是DiskBasedCache,間接朋友就是mRootDirectory、mEntries等。Volley只需要直接和Cache類互動即可,並不需要知道File、mEntries等物件的存在。

文中有引用書本中得例子,也有根據自己理解舉的例子,如有不對還望指出。

相關文章