帶你動手實現 MVP+Clean架構!

MVP + Clean

Clean 架構,有的同窗可能有所耳聞。確定也有至關一部分同窗沒據說過 Clean 架構。git

本篇文章重要講解的是 Clean,MVP 在這裏就再也不贅述,感興趣的戳下方連接。github

從 0 到 1,帶你解剖 MVP 的神祕之處,並本身動手實現 MVP !數據庫

那麼先來解釋一下,何爲 Clean?api

概念

Clean,中文意思爲清潔的、整齊的。因此也能夠稱其爲 "清晰架構"bash

它是一種分層架構方式,將 presentation 層(實現層)、微信

data 層(數據層)以及domain 層(業務邏輯層),彼此獨立。網絡

不一樣層之間,經過接口來鏈接,卻又不瞭解彼此的具體實現。架構

爲何清晰?由於它有五大特性:app

  • 框架獨立
  • 可測試性
  • UI 獨立
  • 數據庫獨立
  • 任何外部代理模塊獨立

放一張圖片,感覺一下,Clean 的獨特魅力: 框架

這裏寫圖片描述

是否是感受很方很晃眼?哈哈哈,不要緊,咱們只須要理解 presentation、domain、data 三層就能夠。

結構

  1. presentation

    在這一層,你可使用 MVC,也可使用 MVP,或者 MVVM。

    注意,在這一層,只作與 UI 相關的邏輯。若是你使用 MVP,那麼,每個 Presenter,

    都至少會有一個 UseCase 組成,他們的主要任務是執行異步任務,獲取數據,

    並經過回調拿到數據,供 UI 渲染。

    因而可知,UseCase 的出現,很大程度上釋放了 Presenter。

  2. domain

    業務邏輯處理層。與外部的交互,經過接口實現。

    簡單理解,項目中的業務會在這一層中,一個一個的會被抽離出來。

    抽離出來,交給誰處理呢?固然是 UseCase,presentation 層須要哪一種業務,

    就經過 UseCase 調用就能夠了。

  3. 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!

相關文章
相關標籤/搜索