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!