Android架構:第四部分-在Android上應用Clean架構,實踐(包含源代碼)(譯)

在Android Architecture系列的最後一部分,咱們將Clean Architecture稍微調整到了Android平臺。 咱們將Android和現實世界從業務邏輯中分離出來,讓滿意的利益相關者滿意,並讓全部事情均可以輕鬆測試。前端

這個理論很好,可是當咱們建立一個新的Android項目時,咱們從哪裏開始? 讓咱們用乾淨的代碼弄髒咱們的手,並將空白的畫布變成一個架構。android

基礎

咱們將首先奠基基礎 - 建立模塊並創建它們之間的依賴關係,以便與依賴規則保持一致。安全

這些將是咱們的模塊,從最抽象的一個到具體的實現:網絡

1. domain

Entities, use cases, repositories interfaces, 和 device interfaces 進入 domain module。架構

理想狀況下,實體和業務邏輯應該是平臺不可知的。 爲了安全起見,爲了防止咱們在這裏放置一些Android的東西,咱們將使它成爲一個純粹的Java模塊。app

2. data

數據模塊應包含與數據持久性和操做相關的全部內容。 在這裏,咱們將找到DAO,ORM,SharedPreferences,網絡相關的東西,例如Retrofit服務和相似的東西。dom

3. device

設備模塊應該包含與Android相關的全部內容,而不是數據持久性和UI。 例如,ConnectivityManager,NotificationManager和misc傳感器的包裝類。ide

咱們將使數據和設備模塊都是Android模塊,由於他們必須瞭解Android而且不能是純Java。函數

4. The easiest part, app module (UI module)

建立項目時,Android模塊已經爲您建立了該模塊。工具

在這裏,您能夠放置與Android UI相關的全部類,例如presenters,controllers,view models,adapters和views。

依賴

依賴規則定義具體模塊依賴於更抽象的模塊。

您可能還記得,從本系列的第三部分能夠看出,UI(應用程序),DB-API(數據)和Device(設備)等東西都在外環中。 這意味着它們處於相同的抽象層次。 咱們如何將它們鏈接在一塊兒呢?

理想狀況下,這些模塊僅取決於域模塊。 在這種狀況下,依賴關係看起來有點像明星:

img

可是,咱們在這裏與Android打交道,事情並不完美。 由於咱們須要建立對象圖並初始化事物,因此模塊有時依賴於domain之外的其餘模塊。

例如,咱們正在app模塊中建立用於依賴注入的對象圖。 這迫使APP模塊瞭解全部其餘模塊。

咱們調整後的依賴關係圖:

img

最後,是時候編寫一些代碼。 爲了更容易,咱們將以RSS Reader APP爲例。 咱們的用戶應該可以管理他們的RSS提要訂閱,從提要中獲取文章並閱讀它們。

Domain

讓咱們從domain層開始,建立咱們的核心業務模型和邏輯。

咱們的商業模式很是簡單:

  • Feed - 持有RSS提要相關數據,如網址,縮略圖網址,標題和說明
  • Article -保存文章相關數據,如文章標題,網址和發佈日期

而對於咱們的邏輯,咱們將使用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獲取提要文章 - >返回提要文章

這裏是數據流問題,用例介於表示層和數據層之間。 咱們如何創建層之間的溝通? 記住那些輸入和輸出端口?

img

咱們的 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。

UI

太棒了,咱們能夠獲取文章,讓咱們如今將它們展現給用戶。

咱們的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的輸出端口,並以這種方式關閉了數據流。 這是咱們前面提到的數據流的具體示例,咱們能夠在流程圖上調整標籤以匹配此示例:

img

咱們的參數P是feedId,返回類型R是文章列表。

您沒必要使用Presenter來處理顯示邏輯,咱們能夠說Clean架構是「前端」不可知的 - 這意味着您可使用MVP,MVC,MVVM或其餘任何東西。

讓咱們在混合中拋出一些Rx

如今,若是你想知道爲何有這樣的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

Data和Device包含業務邏輯不關心的全部實現細節。 它只關心契約類,容許您輕鬆測試它並在不觸及業務邏輯的狀況下交換實施。

在這裏,您可使用本身喜歡的ORM或DAO在本地存儲數據,並使用網絡服務從網絡獲取數據。 咱們將實現FeedService來獲取文章,並使用FeedDao將文章數據存儲在設備上。

每一個數據源(網絡和本地存儲)都將有本身的模型可供使用。

在咱們的例子中,它們是ApiFeed - ApiArticleDbFeed - DbArticle

FeedRepository的具體實現也能夠在Data模塊中找到。

Device模塊將持有做爲NotificationManager類的包裝的通知合同的實現。 咱們也許可使用業務邏輯中的通知來在用戶可能感興趣並推進參與的新文章發佈時向用戶顯示通知。

Models, models everywhere.

您可能已經注意到咱們提到的不只僅是實體或業務模型,還有更多的模型。

實際上,咱們也有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

Part 2: The Clean Architecture

Part 1: every new beginning is hard

相關文章
相關標籤/搜索