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!