開源過程是坎坷的白华,道路是曲折的走越,但前路是光明的椭豫。
歡迎pypr,star旨指,issue赏酥。
開端
整個(gè)工程的開始于2015年。
回顧過去谆构,業(yè)務(wù)層和邏輯層是完全耦合的裸扶。
業(yè)務(wù)快速的迭代很容易令開發(fā)工作變快變糙變莽(技術(shù)水平未達(dá)標(biāo)也是原因之一),一次改版可能要對(duì)原有的代碼做侵入式修改搬素,而根據(jù)開/閉原則呵晨,這是不可接受的魏保。
舊問題
- 由于設(shè)計(jì)欠缺優(yōu)雅性導(dǎo)致功能擴(kuò)展性差
- 缺少單元測(cè)試無法保證程序的魯棒性
- 業(yè)務(wù),邏輯和UI的代碼耦合嚴(yán)重
2016年開始重構(gòu)整個(gè)功能
目標(biāo)
- 業(yè)務(wù)層摸屠,邏輯層和UI層分離
- 只依賴基礎(chǔ)庫
- 易于業(yè)務(wù)擴(kuò)展
- 添加單元測(cè)試
- 支持只使用Fragment
方案
- 使用MVP模式
- 只依賴support v4, v7
- 通過接口和抽象類進(jìn)行依賴反轉(zhuǎn)
- 使用Junit4 + Mokito + Espresso做測(cè)試
talk is free, show me the code.
時(shí)序圖
啟動(dòng)Activity的情況
概要類圖
簡(jiǎn)潔起見谓罗,只畫出兩種關(guān)系,橫線是組合季二,豎線是繼承檩咱。根據(jù)依賴反轉(zhuǎn)原則,橫線指向的應(yīng)該是抽象類或者接口(圓形)胯舷。所以刻蚯,View層,Presenter層和Module層已經(jīng)分離了需纳。
設(shè)計(jì)接口
要只依賴于基礎(chǔ)庫芦倒,圖片加載庫和裁剪庫的實(shí)現(xiàn)就要分離出去,通過接口的形式在內(nèi)部使用不翩。
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);
}
- 加載縮略圖
縮略圖是根據(jù)界面可以知道resize寬高兵扬,不需要回調(diào)。 - 加載原圖
原圖可能很大口蝠,很長(zhǎng)器钟,所有是沒有resize的,通過絕對(duì)路徑去解析顯示妙蔗,同時(shí)提供加載成功/失敗的回調(diào)傲霸。
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);
}
- 開始裁剪,啟動(dòng)者可能是Activity或Fragment眉反,需要額外的裁剪參數(shù)
- 裁剪結(jié)束昙啄,通過Intent取到結(jié)果Uri
分離實(shí)現(xiàn)庫OK。
單元測(cè)試
單元測(cè)試的難易程度與耦合度成正比寸五,簡(jiǎn)而言之梳凛,優(yōu)雅的設(shè)計(jì)令單元測(cè)試的編寫變得簡(jiǎn)單。
構(gòu)造Mock類不再困難重重梳杏,接口方法實(shí)現(xiàn)單一功能韧拒,Presenter層和Model層的測(cè)試通過Junit4 + Mokito實(shí)現(xiàn),UI層通過Espresso實(shí)現(xiàn)十性。
Only Fragment
參考Google MVP叛溢,Activity只作為Presenter和View連接的載體,用Fragment實(shí)現(xiàn)View接口劲适,因此可以單獨(dú)使用Fragment作為功能主體楷掉。
// 初始化AbsBoxingPickerFragment
Boxing.get().setupFragment(AbsBoxingPickerFragment, new Boxing.OnFinishListener() {
@Override
public void onFinish(Intent intent, @Nullable List<BaseMedia> medias) {
// 通過回調(diào)接口取到結(jié)果
}
});
最后總結(jié)
抽象能力非常重要,需要不斷地練習(xí)减响。
UML圖很重要靖诗,強(qiáng)烈推薦Bob大叔的UML:Java程序員指南(雙語版)郭怪,暢快淋漓的閱讀快感。