開源過程是坎坷的,道路是曲折的,但前路是光明的。
歡迎pypr,star,issue。
開端
整個工程的開始于2015年。
回顧過去,業務層和邏輯層是完全耦合的。
業務快速的迭代很容易令開發工作變快變糙變莽(技術水平未達標也是原因之一),一次改版可能要對原有的代碼做侵入式修改,而根據開/閉原則,這是不可接受的。
舊問題
- 由于設計欠缺優雅性導致功能擴展性差
- 缺少單元測試無法保證程序的魯棒性
- 業務,邏輯和UI的代碼耦合嚴重
2016年開始重構整個功能
目標
- 業務層,邏輯層和UI層分離
- 只依賴基礎庫
- 易于業務擴展
- 添加單元測試
- 支持只使用Fragment
方案
- 使用MVP模式
- 只依賴support v4, v7
- 通過接口和抽象類進行依賴反轉
- 使用Junit4 + Mokito + Espresso做測試
talk is free, show me the code.
時序圖
啟動Activity的情況
boxing_sequenece.png
概要類圖
boxing_class.png
簡潔起見,只畫出兩種關系,橫線是組合,豎線是繼承。根據依賴反轉原則,橫線指向的應該是抽象類或者接口(圓形)。所以,View層,Presenter層和Module層已經分離了。
設計接口
要只依賴于基礎庫,圖片加載庫和裁剪庫的實現就要分離出去,通過接口的形式在內部使用。
public interface IBoxingMediaLoader {
/**
* display thumbnail images for a ImageView.
*
* @param img the display ImageView.
* @param absPath the absolute path to display.
* @param width the resize with for the image.
* @param height the resize height for the image.
*/
void displayThumbnail(@NonNull ImageView img, @NonNull String absPath, int width, int height);
/**
* display raw images for a ImageView, need more work to do.
*
* @param img the display ImageView.
* @param absPath the absolute path to display.
* @param callback the callback for the load result.
*/
void displayRaw(@NonNull ImageView img, @NonNull String absPath, IBoxingCallback callback);
}
- 加載縮略圖
縮略圖是根據界面可以知道resize寬高,不需要回調。 - 加載原圖
原圖可能很大,很長,所有是沒有resize的,通過絕對路徑去解析顯示,同時提供加載成功/失敗的回調。
public interface IBoxingCrop {
/***
* start crop operation.
*
* @param cropConfig {@link BoxingCropOption}
* @param path the absolute path of media.
* @param requestCode request code for the crop.
*/
void onStartCrop(Context context, Fragment fragment, @NonNull BoxingCropOption cropConfig,
@NonNull String path, int requestCode);
/**
* get the result of cropping.
*
* @param resultCode the code in {@link android.app.Activity#onActivityResult(int, int, Intent)}
* @param data the data intent
* @return the cropped image uri.
*/
Uri onCropFinish(int resultCode, Intent data);
}
- 開始裁剪,啟動者可能是Activity或Fragment,需要額外的裁剪參數
- 裁剪結束,通過Intent取到結果Uri
分離實現庫OK。
單元測試
單元測試的難易程度與耦合度成正比,簡而言之,優雅的設計令單元測試的編寫變得簡單。
構造Mock類不再困難重重,接口方法實現單一功能,Presenter層和Model層的測試通過Junit4 + Mokito實現,UI層通過Espresso實現。
Only Fragment
參考Google MVP,Activity只作為Presenter和View連接的載體,用Fragment實現View接口,因此可以單獨使用Fragment作為功能主體。
// 初始化AbsBoxingPickerFragment
Boxing.get().setupFragment(AbsBoxingPickerFragment, new Boxing.OnFinishListener() {
@Override
public void onFinish(Intent intent, @Nullable List<BaseMedia> medias) {
// 通過回調接口取到結果
}
});
最后總結
抽象能力非常重要,需要不斷地練習。
UML圖很重要,強烈推薦Bob大叔的UML:Java程序員指南(雙語版),暢快淋漓的閱讀快感。