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等,依賴倒置思想就可以很輕鬆的實現模組替換:
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來選擇時
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;
}
}
然後根據策略模式,設計出影象載入的基本框架:
最後再去實現其他部分,整體方案的設計並不難,涉及到具體實現就需要細心去寫程式碼了。所以在進行面向介面程式設計時候,前期最關鍵的還是架構的設計,如何能夠保證易拓展、易維護、易、易相容等。架構設計好之後就是細節的實現,可以直接使用開源方案來組裝,或者創造輪子再去實現一套新的方案。