在 Android 架構系列的最後部分,咱們將 Clean Architecture 調整到 Android 平臺。咱們將 Android 和真實世界從業務邏輯中分離,令利益相關者滿意,使一切都容易測試。前端
理論很棒,可是當咱們建立一個新 Android 項目時,該從哪開始呢?讓咱們用整潔代碼弄髒咱們的手,把空白畫布變成一個架構。java
讓咱們先作一些基礎工做 —— 建立模塊並創建依賴關係,使其與依賴規則保持一致。android
這些將是咱們的模塊,從抽象到具體:git
實體、用例、倉庫接口和設備接口放入 domain 模塊。github
理想狀況下,實體和業務邏輯應該是平臺無關的。爲了安全起見,爲了防止咱們在這裏放置一些 Android 的東西,咱們將使它成爲一個純 java 模塊。安全
data 模塊應當持有與數據持久化和操做相關的全部內容。在這裏咱們能夠找到 DAO、ORM、SharedPreferences、網絡相關的好比 Retrofit Service 或相似的東西。網絡
device 模塊應該擁有與 Android 相關的全部東西(除了數據持久化 和 UI)。例如 ConnectivityManager, NotificationManager 和 misc 傳感器的封裝類。架構
咱們將使 data 和 device 模塊成爲 Android 模塊,由於它們必須知道 Android,不能是純的 java。app
當你建立項目時,該模塊已經由 Android Studio 爲你建立好了。框架
在這裏,你能夠放置與 Android UI 相關的類,譬如 Presenter,Controller,ViewModel,Adapter 以及 View。
依賴規則定義了具體模塊依賴於抽象模塊。
你可能會記得本系列的第三部分,UI(app),DB-API(data)以及設備(device)的東西都在外層。這意味着它們在同一抽象級別。那麼咱們該如何將它們串在一塊兒呢?
理想的狀況下,這些模塊僅依賴於領域(domain)模塊。在這種狀況下,依賴關係看起來有點像一顆星:
可是這裏咱們涉及 Android 因此事情就不那麼完美了。由於咱們須要建立對象依賴圖以及初始化一些東西,模塊有時依賴另外一個模塊而不是領域模塊。
例如,咱們須要在 app 模塊建立依賴注入的對象依賴圖,這就迫使 app 模塊須要知道其他全部模塊。
咱們調整後的依賴關係圖:
終於,是時候寫些代碼了。爲了容易演示,咱們將以 RSS 閱讀器 app 爲例。咱們的用戶應該可以管理他們的 RSS Feed 訂閱,從 Feed 中獲取文章並閱讀它們。
讓咱們從領域層開始,建立咱們的核心業務模型和邏輯。
咱們的業務模型很是簡單:
至於咱們的邏輯,咱們將使用 UseCase 也就是交互器。它們將簡單類中的小部分業務邏輯封裝起來。它們都會實現通常的 UseCase 協議:
public interface UseCase<P> {
interface Callback {
void onSuccess();
void onError(Throwable throwable);
}
void execute(P parameter, Callback callback);
}
複製代碼
當用戶打開咱們的 app 要作的第一件事情是添加一個新的 RSS Feed 訂閱。因此從咱們的交互器開始,咱們建立 AddNewFeedUseCase
,以及處理 feed 添加和驗證邏輯的輔助類。
AddNewFeedUseCase
使用 FeedValidator
來檢查 feed URL 的有效性,咱們還將建立 FeedRepository
協議,它爲咱們的業務邏輯提供基礎的增刪改查能力來管理 feed 數據:
public interface FeedRepository {
int createNewFeed(String feedUrl);
List<Feed> getUserFeeds();
List<Article> getFeedArticles(int feedId);
boolean deleteFeed(int feedId);
}
複製代碼
注意咱們在領域層的命名是如何清晰地說明咱們的 app 是作什麼的。
把全部東西放在一塊兒,咱們的 AddNewFeedUseCase
看起來像這樣:
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);
}
}
}
複製代碼
爲了簡潔起見,省略了構造函數。
如今,你可能會困惑,爲何咱們的用例和回調是一個接口?
爲了更好地演示咱們下一個問題,讓咱們來研究研究 GetFeedArticlesUseCase
得到 feedId -> 經過 FeedRepository 抓取 feed 文章 -> 返回 feed 文章列表
這是數據流問題,用例位於表現層和數據層之間,咱們怎樣創建起層和層之間的通訊?記得那些輸入和輸出端口嗎?
咱們的用例必須實現輸入端口(interface)。Presenter 調用用例的方法,數據流向用例(feedId)。用例將 feedId 轉換成 feed 文章列表,並但願將其返回給表現層。它擁有指向輸出端口(Callback)的引用,由於輸出端口是定義在同一層的,因此它調用了輸出端口的一個方法。所以數據將發送到輸出端口 —— Presenter。
咱們稍微調整一下 UseCase 協議:
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
實現以下:
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);
}
}
}
複製代碼
最後一件領域層須要注意的事情是,交互器只應該包含業務邏輯。在這樣作時,它們可使用 Repository,組合其它交互器,使用相似咱們例子中 FeedValidator
這樣的公共設施類。
很好,咱們能夠抓取文章,如今讓咱們向用戶展現它們。
咱們的 View 有一個簡單的協議:
interface View {
void showArticles(List<ArticleViewModel> feedArticles);
void showErrorMessage();
void showLoadingIndicator();
}
複製代碼
此 View 的 Presenter 的表現邏輯很是簡單。它抓取文章,轉換成視圖模型傳遞給 View,簡單吧,對嗎?
簡單的 Presenter 是 Clean Architecture 和 表現 —— 業務邏輯分離的另外一個偉大成就。
這是咱們的 FeedArticlesPresenter
:
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 接口,並將自身傳遞給用例,它其實是用例的輸出端口,並以這種方式關閉數據流。這是咱們前面提到過的數據流的具體例子,咱們能夠在流程圖上調整標籤來匹配這個例子:
咱們的參數 P 是整數 feedId,返回類型 R 是文章列表。
你不必定必須使用 Presenter 來處理表現邏輯,咱們能夠說,Clean Architecture 是「前端」無關的 —— 這意味着你可使用 MVP,MVC,MVVM 或其餘任何東西。
若是你想知道爲何會有這樣關於 RxJava 的炒做,那麼來看看咱們的用例的響應式實現:
public interface UseCase<P, R> {
Single<R> execute(P parameter);
}
public interface CompletableUseCase<P> {
Completable execute(P parameter);
}
複製代碼
回調接口如今已經消失,咱們使用 RxJava Single / Completable 接口做爲輸出端口。
響應式 FeedArticlePresenter
的實現以下:
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,Presenter 會實現回調,而使用 RxJava, 訂閱者也包含在外層 —— Presenter 的某個地方。
譯者注:若是你打算用 ViewModel 取代 Presenter,而且在項目中使用了 RxJava,那麼向你安利 使用 RxCommand 在 Android 上實現 MVVM。
data 和 device 模塊包含全部業務邏輯不關心的實現細節。它只關係協議,使你容易測試,以及在不觸及業務邏輯的狀況下更換實現。
這裏,你可使用你喜歡的 ORM 或 DAO 來存儲本地數據,使用你喜歡的網絡服務來從網絡獲取數據。咱們將實現 FeedService
來拉取文章,使用 FeedDao
來存儲文章數據到設備。
每一個數據源(網絡和本地存儲)都有本身的模型來處理。
在咱們的例子中,它們是 ApiFeed - ApiArticle 和 DbFeed - DbArticle。
FeedRepository
的具體實現也能夠在 data 模塊中找到。
device 模塊持有 Notifications 協議的實現,就是對 NotificationManager 類的一個包裝。當有新的用戶可能感興趣並參與的文章發表時,咱們會在咱們的業務邏輯中使用 Notifications 來向用戶顯示一個通知。
你可能已經注意到,咱們說起的模型不只僅是實體或業務。
實際上,咱們還有 db 模型,API 模型,視圖模型,固然還有業務模型。
每一層都有本身的模型是個不錯的實踐,這樣你的具體細節,譬如視圖,就不須要依賴低層實現的具體細節。經過這種方式,舉個例子,當你決定更換 ORM 框架時,你就不須要破壞不相干的代碼。
爲了確保這點,有必要在每一個層中使用對象映射器。在咱們的例子中,咱們使用 ViewModelMapper
將 demain 模塊中的 Article 模型映射成 ArticleViewModel
遵循這些準則,咱們建立了健壯且通用的架構。首先,看起要寫好多代碼,確實也是,可是記住,咱們是爲將來的變化和功能搭建咱們的架構。若是你正確地作了,將來你會感謝當初的決定。
在下一部分中,咱們會介紹多是這個架構中最重要的部分,它的可測試性以及如何測試它。那麼,在此期間,你對架構實現的哪部分最感興趣呢?