1. 程式人生 > >Android 面向介面程式設計

Android 面向介面程式設計

關鍵詞:Android、POP、面向介面程式設計 、面向過程、面向協議

一、概述

面向介面程式設計是面向物件程式設計的一種實現方式,它的核心思想是將抽象與實現分離,從元件的級別來設計程式碼,達到高內聚低耦合的目的。最簡單的面向介面程式設計方法是,先定義底層介面模組,再定義高層實現模組。但是這樣存在一個問題,就是當修改底層介面的時候,高層實現也需要跟著修改,這也違反了開閉原則。 在面相物件設計基本原則(SOLID)中,依賴倒置原則說得就是這個問題。
同時配合使用依賴注入思想,可以很好地處理這個問題。(PS:注意面向介面程式設計的介面並不是狹義上指Java中的介面,而是指超型別,可以是介面也可以是抽象類)

二、依賴倒置&依賴注入

依賴倒置原則是高層模組不應該依賴低層模組,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象。這裡的抽象就是介面或者抽象類,我們應該依賴介面或者抽象類 ,而不是依賴具體的實現來程式設計。它應該遵循如下特性:

  • 模組間的依賴通過抽象發生
  • 實現類之間不發生直接的依賴關係
  • 其依賴關係是通過介面或抽象類產生

依賴注入是非主動初始化依賴物件,而通過外部來傳入依賴的方式,稱為依賴注入。它有幾個好處:

  • 解耦,將依賴之間解耦
  • 方便做單元測試,尤其是Mock測試

依賴倒置通常會通過引入中間層來處理模組間互動,這個中間層相當於一個抽象介面層,高層模組和底層模組都依賴於這個中間層來互動,底層模組改變不會影響到高層模組,這就滿足了開放關閉原則。而且假如高層模組跟底層模組同時處於開發階段,這樣有了中間抽象層之後,每個模組都可以針對這個抽象層的介面同時開發,高層模組就不需要等到底層模組開發完畢才能繼續。舉一個例子,

// 抽象:介面
public interface ImageCache {
        ...   
}

// 錯誤例子:依賴於細節
public class ImageLoader {

        // (直接依賴於細節)
        DoubleCache mCache = new DoubleCache();

        public void displayImage(String url, ImageView imageView) {
                ...
        }

        // (直接依賴於細節)
        public void
setImageCache(DoubleCache cache) { mCache = cache; } } // 正確例子:依賴於抽象 public class ImageLoader { // 依賴於抽象(介面或者抽象類) ImageCache mCache; // 設定ImageCache依賴於抽象 public void setImageCache(ImageCache cache) { mCache = cache; } public void displayImage(String url, ImageView imageView) { ... } } public class Activity{ ImageLoader mImageLoader; mImageLoader.setImageCache(new MemoryCache());// 依賴注入 mImageLoader.displayImage(...); }

上面定義的ImageCache就是抽象(介面),它相當於中間層。同時,在傳入ImageCache的時候,是通過傳入依賴的方式而不是在方法內部生成,這就是依賴注入的思想。

再舉一個例子,比如在專案中有涉及IM的功能,現在這個IM模組採用的是XMPP協議來實現,客戶端通過這個模組來實現訊息的收發,但是假如後面要換成其它協議,比如MQTT等,依賴倒置思想就可以很輕鬆的實現模組替換:

458529-cbf419fb6dbdaed8.png

public interface MessageDelegate{
     void goOnline();
     void sendMessage(String msg);
}

//xmpp實現
public interface XMPPMessageCenter extends MessageDelegate{
     void goOnline();
     void sendMessage(String msg);
}

//MQTT實現
public interface MQTTMessageCenter extends MessageDelegate{
     void goOnline();
     void sendMessage(String msg);
}

//業務層
//使用遵循MessageDelegate協議的物件,針對介面程式設計,以後替換也很方便
public interface BussinessLayer{
     MessageDelegate messageCenter;
     //業務
     messageCenter.goOnline();
     ...
}

三、策略模式

那麼,就很容易聯想到面向介面程式設計的一個典型設計模式,策略模式。 策略模式定義了一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。一般的使用場景如下:

  • 針對同一型別問題的多種處理方式,僅僅是具體行為有差別時。
  • 需要安全的封裝多種同一型別的操作時。
  • 出現同一抽象多個子類,而又需要使用if-else 或者 switch-case來選擇時

strategy-kerison-uml.png

Context用來操作策略的上下文環境,Strategy是策略的抽象,ConcreteStrategyA、ConcreteStrategyB等是具體的策略實現。

四、核心要點

1.封裝變化

找出程式中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起。

如何區分變化的和不會變化的就尤為重要,可以簡單定義為一切有彈性的無法確定的就作為變化的。舉個例子,影象載入的方法就可以作為確定的不會變化的,而影象快取策略有檔案快取、記憶體快取等等,那麼就可以作為變化的。所以,快取相關部分的程式碼就需要獨立出來,不跟其他程式碼混在一起。

2.將行為轉為屬性

區分好變化與不變化部分之後,將變化的部分抽象為介面或者抽象類 ,然後在呼叫處轉為屬性。

在呼叫處,將介面或者抽象類轉為屬性,也就是宣告為成員變數,這樣在方法中具體呼叫的時候,就會根據行為實現的不同而產生不同的結果。

五、實際應用

在安卓開發中,有各種基礎功能的類庫,比如網路請求、影象載入、日誌輸出、資料儲存等等。一般情況下,開源社群也有比較成熟的實現方案,專案有時候也會使用不同的方案。那麼,如何定義一個架構,既可以自己去實現開發方案,同時也可以使用其他方案呢?答案就是利用策略模式,同時配合使用建造者模式、單例模式等,根據面向介面程式設計的思想去完成。下面以影象載入功能為例,去實現一個影象載入類庫。

目前比較流行的影象載入類庫有Glide、Fresco、Picasso、UML等,從對這些類庫的使用來看,對外提供的功能介面很多都比較類似,例如影象載入、快取清理、額外配置等等。因此就可以從這些類庫中提出公關介面部分出來,形成一個基礎影象載入架構,然後再繼續進行適配。先各自看一下載入影象的API方法:

1、Glide

Glide.with(getContext()).load(url).skipMemoryCache(true).placeholder(drawable).centerCrop().animate(animator).into(img);

2、Fresco

Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
    .setResizeOptions(new ResizeOptions(width, height))
    .build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
    .setOldController(mDraweeView.getController())
    .setImageRequest(request)
    .build();
mSimpleDraweeView.setController(controller);

3、Picasso

 Picasso.with(context).load(url).resize(50, 50).centerCrop().into(imageView);

4、Universal Image Loader

ImageLoader.getInstance().displayImage(imageUri, imageView, options, new ImageLoadingListener() {
    @Override
    public void onLoadingStarted(String imageUri, View view) {
        ...
    }
    @Override
    public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
        ...
    }
    @Override
    public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
        ...
    }
    @Override
    public void onLoadingCancelled(String imageUri, View view) {
        ...
    }
}, new ImageLoadingProgressListener() {
    @Override
    public void onProgressUpdate(String imageUri, View view, int current, int total) {
        ...
    }
});

從上面可以發現,Glide和Picasso的使用方式幾乎是一致的,通過鏈式呼叫,進行各自影象載入的配置,如快取策略,動畫,佔位符等等。Universal Image Loader方法比較常規,通過單例模式進行影象載入方法的呼叫,方法引數包含有載入配置、回撥介面等。而Fresco有一套自己的邏輯,把影象載入的邏輯封裝到了UI中。對於圖片載入而言,有最基本最重要的必選項,以及可有可無的可選項,從上面方法中提取必選項以及可選項:

  • 必選項:上下文環境(Context),URI(圖片來源),ImageView(圖片容器)
  • 可選項:Options (是否快取、影象大小、圓角、動畫、回撥、預設圖等等)

那麼可以這樣設計介面,

public interface ImageLoaderStrategy{

     void showImage(ImageView imageview, String url, ImageLoaderOptions options);
     void showImage(ImageView imageview, int drawable, ImageLoaderOptions options);

}

當然對於必選項與可選項其實並沒有嚴格的規範,例如Fresco的特殊設計,自己實現了圖片容器而不是ImageView,這時候要麼再新增一個方法:

void showImage(View view, int drawable, ImageLoaderOptions options);

要麼就可以進一步拆分,把View和URI也加入到可選項中,然後使用泛型來動態設定可選項,如下:

public interface ImageLoaderStrategy<T extends ImageLoaderOptions> {
    void loadImage(Context ctx, T options);
}

ImageLoaderOptions就是可選項,這些可選項可以從開源類庫中提出公共的部分,由於這些屬性都是可選擇的,因此最好使用Builder模式來構建。

public class ImageLoaderOptions{

    protected String url;
    protected ImageView imageView;
    protected int placeholder;
    protected int errorPic;

    public String getUrl() {
        return url;
    }

    public ImageView getImageView() {
        return imageView;
    }

    public int getPlaceholder() {
        return placeholder;
    }

    public int getErrorPic() {
        return errorPic;
    }

}

然後根據策略模式,設計出影象載入的基本框架:

cda13f2eab9c573672e1.png

最後再去實現其他部分,整體方案的設計並不難,涉及到具體實現就需要細心去寫程式碼了。所以在進行面向介面程式設計時候,前期最關鍵的還是架構的設計,如何能夠保證易拓展、易維護、易、易相容等。架構設計好之後就是細節的實現,可以直接使用開源方案來組裝,或者創造輪子再去實現一套新的方案。