MVP + Clean
Clean 架構,有的同學可能有所耳聞。肯定也有相當一部分同學沒聽說過 Clean 架構。
本篇文章重要講解的是 Clean,MVP 在這裡就不再贅述,感興趣的戳下方連結。
從 0 到 1,帶你解剖 MVP 的神祕之處,並自己動手實現 MVP !
那麼先來解釋一下,何為 Clean?
概念
Clean,中文意思為清潔的、整齊的。所以也可以稱其為 “清晰架構”。
它是一種分層架構方式,將 presentation 層(實現層)、
data 層(資料層)以及domain 層(業務邏輯層),彼此獨立。
不同層之間,通過介面來連線,卻又不瞭解彼此的具體實現。
為什麼清晰?因為它有五大特性:
- 框架獨立
- 可測試性
- UI 獨立
- 資料庫獨立
- 任何外部代理模組獨立
放一張圖片,感受一下,Clean 的獨特魅力:
是不是感覺很方很晃眼?哈哈哈,沒關係,我們只需要理解 presentation、domain、data 三層就可以。
結構
-
presentation
在這一層,你可以使用 MVC,也可以使用 MVP,或者 MVVM。
注意,在這一層,只做與 UI 相關的邏輯。如果你使用 MVP,那麼,每一個 Presenter,
都至少會有一個 UseCase 組成,他們的主要任務是執行非同步任務,獲取資料,
並通過回撥拿到資料,供 UI 渲染。
由此可見,UseCase 的出現,很大程度上釋放了 Presenter。
-
domain
業務邏輯處理層。與外部的互動,通過介面實現。
簡單理解,專案中的業務會在這一層中,一個一個的會被抽離出來。
抽離出來,交給誰處理呢?當然是 UseCase,presentation 層需要哪種業務,
就通過 UseCase 呼叫就可以了。
-
data
這一層,也很好理解,就是獲取資料的地方。
每個業務會對應一個 Repository,具體獲取資料的操作就在這裡。
外部與 data 層連線,是通過 Resporitory。外部不需要知道資料是從哪裡獲取,
不管是從本地,還是從網路,外部也不需要關心。
需求假設
使用者點選 Button 按鈕,獲取微信精選文章,在介面顯示。
需求拆分:
獲取微信精選文章,可以定義為一個業務。
在 presentation 層,點選了 Button 按鈕,向 Presenter 層,發出獲取文章指令 getArticleList()
,
Presenter 層收到指令,呼叫 UseCase.execute()
,這時就與 domain 層產生聯絡了。
在 execute 方法中,通過 Repository 介面從 data 層獲取資料。
獲取資料後,再通過回撥,最終 presentation 層拿到資料,進行文章列表展示。
程式碼實現
建立 ArticleView
public interface ArticleView extends IView {
void getArticleSuccess(List<ArticleBean> articleBeanList);
void getArticleFail(String failMsg);
}
複製程式碼
建立 ArticlePresenter
public class ArticlePresenter extends BasePresenter<ArticleView> {
public void getArticleList(String key) {
}
}
複製程式碼
上面我們說過,Presenter
層是由一個或多個 UseCase
組成,
他們的主要任務是執行非同步任務,獲取資料。那麼我們在 domain
層定義一個 UseCase
,
名為 ArticleCase
,詳細程式碼如下:
public class ArticleCase extends UseCase<List<ArticleBean>, String> {
private ArticleRepository articleRepository;
public ArticleCase(ArticleRepository repository) {
this.articleRepository= repository;
}
@Override
protected Observable<List<ArticleBean>> buildObservable(String s) {
return articleRepository.getArticleList(s);
}
}
複製程式碼
UseCase 是封裝好的一個 Case 基類:
/**
* Case 基類
*
* @param <T> 返回資料
* @param <Params> 請求引數
*/
public abstract class UseCase<T, Params> {
private final CompositeDisposable mDisposables;
public UseCase() {
this.mDisposables = new CompositeDisposable();
}
@SuppressLint("CheckResult")
public void execute(Params params, Consumer nextConsumer, Consumer errorConsumer) {
Observable<T> observable = this.buildObservable(params);
addDisposable(observable.subscribe(nextConsumer, errorConsumer));
}
protected abstract Observable<T> buildObservable(Params params);
private void addDisposable(Disposable disposable) {
mDisposables.add(disposable);
}
@SuppressLint("CheckResult")
public void execute(Params params, BaseObserver<T> observer) {
Observable<T> observable = this.buildObservable(params);
observable.subscribe(observer);
addDisposable(observer.getDisposable());
}
public void dispose() {
if (!mDisposables.isDisposed()) {
mDisposables.dispose();
}
}
}
複製程式碼
任何一個業務類,都需要去繼承 UseCase
,並實現 buildObservable
方法。
繼續看 ArticleCase
,我們用到了介面 ArticleRepository
,很明顯,
這個介面用於被 data 層實現,從而獲取資料並回撥。
public interface ArticleRepository {
Observable<List<ArticleBean>> getArticleList(String param);
}
複製程式碼
接下來在 data 層,去實現 ArticleRepository
:
public class ArticleRepositoryImpl implements ArticleRepository {
private DataApi mApi;
public ArticleRepositoryImpl() {
mApi = JDHttp.createApi(DataApi.class);
}
@Override
public Observable<List<ArticleBean>> getArticleList(String param) {
return mApi.getArticleList(param).compose(JDTransformer.<BaseResponse>switchSchedulers())
.map(new Function<BaseResponse, List<ArticleBean>>() {
@Override
public List<ArticleBean> apply(BaseResponse baseResponse) throws Exception {
return baseResponse.getResult().getList();
}
});
}
}
複製程式碼
在這裡,進行了獲取資料操作。無論是從網路、還是本地獲取,domain
層不需要知道。
然後在 Presenter
層中實現 ArticleCase
,並呼叫 execute()
方法,獲取資料。
public class ArticlePresenter extends BasePresenter<ArticleView> {
private ArticleCase mCase;
public void getData(String key) {
mCase.execute(key, new BaseObserver<List<ArticleBean>>() {
@Override
public void onSuccess(List<ArticleBean> articleBeanList) {
getView().getArticleSuccess(articleBeanList);
}
@Override
public void onFail(String failMsg) {
getView().getArticleFail(failMsg);
}
});
}
@Override
public List<UseCase> createCases() {
mCase = new ArticleCase(new ArticleRepositoryImpl());
mCaseList.add(mCase);
return mCaseList;
}
}
複製程式碼
並且在 BasePresenter
中實現自動取消訂閱:
public abstract class BasePresenter<V extends IView> implements IPresenter<V> {
private V mView;
private CPBridge mBridge = new CPBridge();
protected List<UseCase> mCaseList = new ArrayList<>();
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void attach(V view) {
this.mView = view;
createCases();
mCaseList.forEach(new Consumer<UseCase>() {
@Override
public void accept(UseCase useCase) {
mBridge.addCase(useCase);
}
});
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void detach() {
this.mView = null;
mBridge.removeCase();
}
protected abstract List<UseCase> createCases();
public V getView() {
if (isViewAttached()) {
return mView;
} else {
throw new IllegalStateException("Please call IPresenter.attach(IView view) before requesting data");
}
}
private boolean isViewAttached() {
return null != mView;
}
}
複製程式碼
感覺繞嗎?沒關係,結合簡單的業務,將程式碼反覆的敲幾次,你就明白了,相信我。
具體程式碼實現請看 demo。
總結
通過上面的程式碼,實現一個業務,雖然寫了好多類,好多程式碼。
但是這樣寫的好處也是顯而易見的:整體架構更加清晰、易維護、方便測試、高內聚、低耦合。
同時也希望閱讀過這篇文章的人,可以親手實踐。通過實踐,你會明白很多之前不明白的問題。
希望這篇文章能對你有用,謝謝。
最後,附上 demo 地址:MVP-Clean-Demo
您的 star 是我前進的動力,歡迎 star!