1、clean架构简介
clean架构相信大家早有耳闻,毕竟是Bob大叔的心血之作,最近又把http://five.agency/blog/android/ 几篇关于clean 架构的文章拿出来读读加深了对该架构的一些理解。clean架构又称“洋葱架构”,这个是由于它的架构示意图得名的
删除一些android项目中使用不到的东西加上我们使用的东西看起来如下图
2、核心概念
从最抽象的核心到细节的边缘
EntitiesEntities,
即Domain 对象或业务对象,是App的核心。 它们代表了APP的主要功能, 它们包含业务逻辑, 他们不会与外在世界的细节进行互动。
Use case,
又名interactors(交互器),又名business services,是Entities的扩展,是业务逻辑的延伸,也就是说。 它们包含的逻辑不仅限于一个实体,而是处理更多的实体。
Repositories
Repositories用于持久化Entities。 就这么简单。 它们被定义为接口并用作想要对Entities执行CRUD操作的用例的输出端口。
Presenters
如果你熟悉MVP模式,Presenters就会做你期望他们做的事情。 他们处理用户交互,调用适当的业务逻辑,并将数据发送到UI进行渲染。 这里通常有不同类型之间的映射
大概这几个概念可以帮助我们更好的理解clean 架构,但是核心一点就是越在洋葱里面越是抽象。
3、clean 架构的核心规则
首先需要掌握涉及的领域,原文称作“Master of your domain”,就是说当你打开你的工程,除去技术相关,你就应该从宏观上知晓你的app是做什么的,而不要沉浸到细节之中” 为啥需要我们站在这个高度去看待自己的项目呢,因为这也是clean核心所在,该架构越往洋葱里面抽象度越来越高,都是业务逻辑的高度抽象,即”内层包含业务逻辑、外层包含实现细节”
继而给出了设计出这样特性的clean架构需要具备的技术点
(1)Dependency rule(依赖原则)
(2)Abstraction(抽象)
(3)Communication between layers(层之间的通信)
下面将一一介绍
3.1 Dependency rule(依赖原则)
从最开始的第一幅我们就可以看出箭头是”依赖”,即 外层看到并了解内层,但是内层既看不到也不知道外层,结合前面所强调的 内层包含业务逻辑(抽象),外层包含实现细节。 结合依赖关系规则,业务逻辑既看不到,也不知道实现细节。 通常我们可以通过放在不同的module中去来调整它们之间的依赖关系
3.2 Abstraction(抽象)
之前也说过当你走向图的中间时,东西变得更加抽象。 这是有道理的:抽象往往比细节更加稳定,就像搭积木一样根基越稳越好。
举个例子比如我们可以将抽象接口定义为“加载网络图片”并将其放入内层,业务逻辑就可以使用它来显示图片。 另一方面,我们可以通过实现改接口调用具体的图片加载库glide或者Picasso,然后将该实现放入外层。业务逻辑可以使用加载图片功能而无需知道实现细节的任何内容。这样抵御了变化的风险,日后换成什么方式加载图片,洋葱内部的抽象业务逻辑是不会感知到的
3.3 Communication between layers(层之间的通信)
既然已经划分好了层,分离了内容,将业务逻辑放在应用程序的架构中心和架构边缘的实现细节中,一切看起来都很棒。 但是你可能很快遇到了一个有趣的问题。
如果你的UI是一个实现细节,那么internet也是一个实现细节,业务逻辑介于两者之间,我们如何从internet获取数据,通过业务逻辑传递它,然后将其发送到屏幕?
组合和继承的就要发挥功效了
4、实践
下面用代码示意一下,以RSS Reader 为例我们的用户应该能够管理他们的RSS提要订阅,从提要中获取文章并阅读它们。这里是数据流问题,用例介于表示层和数据层之间。 我们如何建立层之间的沟通? 记住那些输入和输出端口?
从上图可以看出我们的 Use Case必须实现输入端口(接口)。 Presenter在 Use Case上调用方法,数据流向 Use Case(feedId)。 Use Case映射feedId提供文章并希望将它们发送回表示层。 它有一个对输出端口(回调)的引用,因为输出端口是在同一层定义的,因此它调用了一个方法。 因此,数据发送到输出端口 - Presenter。
让我们从domain层开始,创建我们的核心业务模型和逻辑。
我们的商业模式非常简单:
Feed - 持有RSS提要相关数据,如网址,缩略图网址,标题和说明
Article -保存文章相关数据,如文章标题,网址和发布日期
我们的逻辑,我们将使用UseCases。 他们在简洁的类中封装了小部分业务逻辑。 他们都将实施通用的UseCase 契约接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public interface UseCase
{
interface Callback
{
void onSuccess(R return);
void onError(Throwable throwable);
}
void execute(P parameter, Callback
callback);
}
public interface CompletableUseCase
{
interface Callback{
void onSuccess();
void onError(Throwable throwable);
}
void execute(P parameter, Callback callback);
}
UseCase接口是输入端口,Callback接口是输出端口,GetFeedArticlesUseCase实现如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class GetFeedArticlesUseCase implements UseCase
>
{
private final FeedRepository feedRepository;
@Override
public void execute(final Integer feedId, final Callback>
callback){
try {
callback.onSuccess(feedRepository.getFeedArticles(feedId));
} catch (final Throwable throwable) {
callback.onError(throwable);
}
}
}
然后到洋葱外面的UI实现,View有一个简单的契约类
1
2
3
4
5
6
7
8
9interface View{
void showArticles(List
feedArticles);
void showErrorMessage();
void showLoadingIndicator();
}
该视图的Presenter具有非常简单的显示逻辑。 它获取文章,将它们映射到view odels并传递到View,再看下FeedArticlesPresenter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21class FeedArticlesPresenter implements UseCase.Callback>
{
private final GetFeedArticlesUseCase getFeedArticlesUseCase;
private final ViewModeMapper viewModelMapper;
public void fetchFeedItems(final int feedId){
getFeedArticlesUseCase.execute(feedId, this);
}
@Override
public void onSuccess(final List
articles){
getView().showArticles(viewModelMapper.mapArticlesToViewModels(articles));
}
@Override
public void onError(final Throwable throwable){
getView().showErrorMessage();
}
}
到这里可以看出FeedArticlesPresenter实现了Callback接口,并将其自身传递给use case,它实际上是use case的输出端口,并以这种方式关闭了数据流
谷歌架构项目上也有个 todo-mvp-clean分支可以看下具体玩法。
5、小结
这个跟设计模式中依赖倒置和接口隔离有着密不可分的关系,通过依赖倒置,只依赖于抽象而不是细节,将细节的实现倒置到实现类中,这样洋葱的核心就是清清爽爽的业务逻辑(抽象);
通常一个良好的架构一般需要满足以下几点:
Satisfy a multitude of stakeholders.(满足各方利益者)
Encourage separation of concerns.(鼓励的关注点分离)
Run away from the real world (Android, DB, Internet…).即高度的抽象
Enable your components to be testable.(使您的组件成为可测试的)
但是一个架构往往针对特定的场景,架构也是需要慢慢演进的,比如后面的模块化、插件化等等都是业务发展到一定程度,当前架构的弊端慢慢的凸显需要更新。但不管如何变化,一些核心基本点还是相伴相随,比如依赖翻转、面向接口编程、关注点分离等都是我们需要点亮的技能点之一。
##参考