clean架构

1、clean架构简介

clean架构相信大家早有耳闻,毕竟是Bob大叔的心血之作,最近又把http://five.agency/blog/android/ 几篇关于clean 架构的文章拿出来读读加深了对该架构的一些理解。clean架构又称“洋葱架构”,这个是由于它的架构示意图得名的

删除一些android项目中使用不到的东西加上我们使用的东西看起来如下图

2、核心概念

从最抽象的核心到细节的边缘

Entities

  • Entities,
    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
25
public interface UseCase<P, R>
{

interface Callback<R>
{
void onSuccess(R return);
void onError(Throwable throwable);
}

void execute(P parameter, Callback<R>
callback);
}



public interface CompletableUseCase<P>
{

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
16
class GetFeedArticlesUseCase implements UseCase
<Integer, List<Article>>
{

private final FeedRepository feedRepository;

@Override
public void execute(final Integer feedId, final Callback<List<Article>>
callback) {
try {
callback.onSuccess(feedRepository.getFeedArticles(feedId));
} catch (final Throwable throwable) {
callback.onError(throwable);
}
}
}

然后到洋葱外面的UI实现,View有一个简单的契约类

1
2
3
4
5
6
7
8
9
interface View {

void showArticles(List<ArticleViewModel>
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
21
class FeedArticlesPresenter implements UseCase.Callback<List<Article>>
{

private final GetFeedArticlesUseCase getFeedArticlesUseCase;
private final ViewModeMapper viewModelMapper;

public void fetchFeedItems(final int feedId) {
getFeedArticlesUseCase.execute(feedId, this);
}

@Override
public void onSuccess(final List<Article>
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、小结

这个跟设计模式中依赖倒置和接口隔离有着密不可分的关系,通过依赖倒置,只依赖于抽象而不是细节,将细节的实现倒置到实现类中,这样洋葱的核心就是清清爽爽的业务逻辑(抽象);

通常一个良好的架构一般需要满足以下几点:

  1. Satisfy a multitude of stakeholders.(满足各方利益者)
  2. Encourage separation of concerns.(鼓励的关注点分离)
  3. Run away from the real world (Android, DB, Internet…).即高度的抽象
  4. Enable your components to be testable.(使您的组件成为可测试的)

但是一个架构往往针对特定的场景,架构也是需要慢慢演进的,比如后面的模块化、插件化等等都是业务发展到一定程度,当前架构的弊端慢慢的凸显需要更新。但不管如何变化,一些核心基本点还是相伴相随,比如依赖翻转、面向接口编程、关注点分离等都是我们需要点亮的技能点之一。

##参考

1、http://five.agency/blog/android/
2、http://five.agency/android-architecture-part-1-every-new-beginning-is-hard/
3、http://five.agency/android-architecture-part-2-clean-architecture/

0%