在Android Architecture系列的最后一部分,咱们将Clean Architecture稍微调整到了Android平台。 咱们将Android和现实世界从业务逻辑中分离出来,让满意的利益相关者满意,并让全部事情均可以轻松测试。前端
这个理论很好,可是当咱们建立一个新的Android项目时,咱们从哪里开始? 让咱们用干净的代码弄脏咱们的手,并将空白的画布变成一个架构。android
咱们将首先奠基基础 - 建立模块并创建它们之间的依赖关系,以便与依赖规则保持一致。安全
这些将是咱们的模块,从最抽象的一个到具体的实现:网络
Entities, use cases, repositories interfaces, 和 device interfaces 进入 domain module。架构
理想状况下,实体和业务逻辑应该是平台不可知的。 为了安全起见,为了防止咱们在这里放置一些Android的东西,咱们将使它成为一个纯粹的Java模块。app
数据模块应包含与数据持久性和操做相关的全部内容。 在这里,咱们将找到DAO,ORM,SharedPreferences,网络相关的东西,例如Retrofit服务和相似的东西。dom
设备模块应该包含与Android相关的全部内容,而不是数据持久性和UI。 例如,ConnectivityManager,NotificationManager和misc传感器的包装类。ide
咱们将使数据和设备模块都是Android模块,由于他们必须了解Android而且不能是纯Java。函数
建立项目时,Android模块已经为您建立了该模块。工具
在这里,您能够放置与Android UI相关的全部类,例如presenters,controllers,view models,adapters和views。
依赖规则定义具体模块依赖于更抽象的模块。
您可能还记得,从本系列的第三部分能够看出,UI(应用程序),DB-API(数据)和Device(设备)等东西都在外环中。 这意味着它们处于相同的抽象层次。 咱们如何将它们链接在一块儿呢?
理想状况下,这些模块仅取决于域模块。 在这种状况下,依赖关系看起来有点像明星:
可是,咱们在这里与Android打交道,事情并不完美。 由于咱们须要建立对象图并初始化事物,因此模块有时依赖于domain之外的其余模块。
例如,咱们正在app模块中建立用于依赖注入的对象图。 这迫使APP模块了解全部其余模块。
咱们调整后的依赖关系图:
最后,是时候编写一些代码。 为了更容易,咱们将以RSS Reader APP为例。 咱们的用户应该可以管理他们的RSS提要订阅,从提要中获取文章并阅读它们。
让咱们从domain层开始,建立咱们的核心业务模型和逻辑。
咱们的商业模式很是简单:
而对于咱们的逻辑,咱们将使用UseCases。 他们在简洁的类中封装了小部分业务逻辑。 他们都将实施通用的UseCase 契约类:
1 2 3 4 5 6 7 8 9 10 |
public interface UseCase<P> { interface Callback { void onSuccess(); void onError(Throwable throwable); } void execute(P parameter, Callback callback); } |
咱们的用户在打开咱们的应用时首先要作的就是添加一个新的RSS订阅。 所以,要开始使用咱们的Use Case,咱们将建立AddNewFeedUseCase及其助手来处理Feed的添加和验证逻辑。
AddNewFeedUseCase将使用FeedValidator来检查Feed URL的有效性,而且咱们还将建立FeedRepository 契约类,这将为咱们的业务逻辑提供一些基本的CRUD功能来管理供稿数据:
1 2 3 4 5 6 7 8 9 10 |
public interface FeedRepository { int createNewFeed(String feedUrl); List<Feed> getUserFeeds(); List<Article> getFeedArticles(int feedId); boolean deleteFeed(int feedId); } |
请注意咱们在Domain层的命名是如何清楚地传递咱们的APP正在作什么的想法。
把全部东西放在一块儿,咱们的AddNewFeedUseCase看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public final class AddNewFeedUseCase implements UseCase<String> { private final FeedValidator feedValidator; private final FeedRepository feedRepository; @Override public void execute(final String feedUrl, final Callback callback) { if (feedValidator.isValid(feedUrl)) { onValidFeedUrl(feedUrl, callback); } else { callback.onError(new InvalidFeedUrlException()); } } private void onValidFeedUrl(final String feedUrl, final Callback callback) { try { feedRepository.createNewFeed(feedUrl); callback.onSuccess(); } catch (final Throwable throwable) { callback.onError(throwable); } } } |
ps:为简洁起见,省略构造函数。
如今,您可能想知道,为何咱们的use case以及咱们的回调是一个接口?
为了更好地展现咱们的下一个问题,让咱们来研究GetFeedArticlesUseCase。
它须要一个feedId - >经过FeedRespository获取提要文章 - >返回提要文章
这里是数据流问题,用例介于表示层和数据层之间。 咱们如何创建层之间的沟通? 记住那些输入和输出端口?
咱们的 Use Case必须实现输入端口(接口)。 Presenter在 Use Case上调用方法,数据流向 Use Case(feedId)。 Use Case映射feedId提供文章并但愿将它们发送回表示层。 它有一个对输出端口(回调)的引用,由于输出端口是在同一层定义的,所以它调用了一个方法。 所以,数据发送到输出端口 - Presenter。
咱们将稍微调整咱们的UseCase契约类:
1 2 3 4 5 6 7 8 9 |
public interface UseCase<P, R> { interface Callback<R> { void onSuccess(R return); void onError(Throwable throwable); } void execute(P parameter, Callback<R> callback); } |
1 2 3 4 5 6 7 8 9 |
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 |
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); } } } |
在Domain层中要注意的最后一件事是Interactors应该只包含业务逻辑。 在这样作的时候,他们可使用存储库,结合其余交互器,并在咱们的例子中使用一些实用工具对象,如FeedValidator。
太棒了,咱们能够获取文章,让咱们如今将它们展现给用户。
咱们的View有一个简单的契约类:
1 2 3 4 5 6 7 8 |
interface View { void showArticles(List<ArticleViewModel> feedArticles); void showErrorMessage(); void showLoadingIndicator(); } |
该视图的Presenter具备很是简单的显示逻辑。 它获取文章,将它们映射到view odels并传递到View,很简单,对吧?
简单的presenters是Clean架构和presentation - business逻辑分离的又一壮举。
这里是咱们的FeedArticlesPresenter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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的输出端口,并以这种方式关闭了数据流。 这是咱们前面提到的数据流的具体示例,咱们能够在流程图上调整标签以匹配此示例:
咱们的参数P是feedId,返回类型R是文章列表。
您没必要使用Presenter来处理显示逻辑,咱们能够说Clean架构是“前端”不可知的 - 这意味着您可使用MVP,MVC,MVVM或其余任何东西。
如今,若是你想知道为何有这样的RxJava,咱们将看看咱们UseCase的反应式实现:
1 2 3 4 |
public interface UseCase<P, R> { Single<R> execute(P parameter); } |
1 2 3 4 |
public interface CompletableUseCase<P> { Completable execute(P parameter); } |
回调接口如今不见了,咱们使用RxJava Single / Completable接口做为咱们的输出端口。
Reactive GetFeedArticlesUseCase:
1 2 3 4 5 6 7 8 9 |
class GetFeedArticlesUseCase implements UseCase<Integer, List<Article>> { private final FeedRepository feedRepository; @Override public Single<List<Article>> execute(final Integer feedId) { return feedRepository.getFeedArticles(feedId); } } |
Reactive FeedArticlePresenter 以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class FeedArticlesPresenter { private final GetFeedArticlesUseCase getFeedArticlesUseCase; private final ViewModeMapper viewModelMapper; public void fetchFeedItems(final int feedId) { getFeedItemsUseCase.execute(feedId) .map(feedViewModeMapper::mapFeedItemsToViewModels) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::onSuccess, this::onError); } private void onSuccess(final List articleViewModels) { getView().showArticles(articleViewModels); } private void onError(final Throwable throwable) { getView().showErrorMessage(); } } |
虽然它有点隐藏,但一样的数据流反演原理仍然存在,由于没有RxJava Presenters实现回调,而且RxJava订阅者也包含在外层 - 在Presenter的某处。
Data和Device包含业务逻辑不关心的全部实现细节。 它只关心契约类,容许您轻松测试它并在不触及业务逻辑的状况下交换实施。
在这里,您可使用本身喜欢的ORM或DAO在本地存储数据,并使用网络服务从网络获取数据。 咱们将实现FeedService来获取文章,并使用FeedDao将文章数据存储在设备上。
每一个数据源(网络和本地存储)都将有本身的模型可供使用。
在咱们的例子中,它们是ApiFeed - ApiArticle和DbFeed - DbArticle。
FeedRepository的具体实现也能够在Data模块中找到。
Device模块将持有做为NotificationManager类的包装的通知合同的实现。 咱们也许可使用业务逻辑中的通知来在用户可能感兴趣并推进参与的新文章发布时向用户显示通知。
您可能已经注意到咱们提到的不只仅是实体或业务模型,还有更多的模型。
实际上,咱们也有db模型,API模型,View模型,固然还有业务模型。
对于每一个图层来讲,都有一个很好的实践,可使用它本身的模型,所以具体的细节(如View)不依赖于较低层实现的具体细节。 这样,例如,若是您决定从一个ORM更改成另外一个,则没必要分解不相关的代码。
为了实现这一点,有必要在每一个图层中使用对象映射器。 在示例中,咱们使用ViewModelMapper将Domain 里的Article模型映射到ArticleViewModel。
遵循这些准则,咱们建立了一个强大且多功能的架构。 起初,它可能看起来像不少代码,它有点像,但请记住,咱们正在构建咱们的架构以适应将来的变化和功能。 若是你作得对,将来你会感恩。
在下一部分中,咱们将会介绍这个架构中最重要的部分,可测试性以及如何测试它。
那么,在此期间,您最感兴趣的是架构实现的哪一部分?
这是Android Architecture系列的一部分。 检查咱们的其余部分:
Part 4: Applying Clean Architecture on Android (Hands-on)
Part 3: Applying Clean Architecture on Android