做自(zì)由與創造的先行者

Android網域層

Android開(kāi)發手冊

網域層是位于界面層和(hé)數據層之間的可選層。

網域層負責封裝複雜(zá)的業務邏輯,或者由多個 ViewModel 重複使用(yòng)的簡單業務邏輯。此層是可選的,因爲并非所有應用(yòng)都有這(zhè)類需求。因此,您應僅在需要時(shí)使用(yòng)該層,例如處理(lǐ)複雜(zá)邏輯或支持可重用(yòng)性。

網域層具有以下(xià)優勢:

避免代碼重複。

改善使用(yòng)網域層類的類的可讀性。

改善應用(yòng)的可測試性。

讓您能(néng)夠劃分好(hǎo)職責,從(cóng)而避免出現(xiàn)大(dà)型類。

爲了(le)使這(zhè)些(xiē)類保持簡單輕量化,每個用(yòng)例都應僅負責單個功能(néng),且不應包含可變數據。您應在界面或數據層中處理(lǐ)可變的數據。

注意:本頁中提供的建議(yì)和(hé)最佳實踐可應用(yòng)于各種應用(yòng)。遵循這(zhè)些(xiē)建議(yì)和(hé)最佳實踐可以提升應用(yòng)的可擴展性、質量和(hé)穩健性,并可使應用(yòng)更易于測試。不過,您應該将這(zhè)些(xiē)提示視(shì)爲指南,并視(shì)需要進行調整來(lái)滿足您的要求。

本指南中的命名慣例

在本指南中,用(yòng)例以其負責的單一操作(zuò)命名。具體命名慣例如下(xià):

一般現(xiàn)在時(shí)動詞 + 名詞/内容(可選)+ 用(yòng)例。

例如:FormatDateUseCase、LogOutUserUseCase、GetLatestNewsWithAuthorsUseCase 或 MakeLoginRequestUseCase。

依賴關系

在典型的應用(yòng)架構中,用(yòng)例類适合界面層的 ViewModel 與數據層的代碼庫。這(zhè)意味着用(yòng)例類通常依賴于倉庫類,并且它們與界面層的通信方式與倉庫的通信方式相同 - 使用(yòng)回調(Java 代碼)或協程(Kotlin 代碼)。如需了(le)解詳情,請(qǐng)參閱數據層頁面。

例如,在您的應用(yòng)中,可能(néng)會(huì)有一個用(yòng)例類,用(yòng)于從(cóng)新聞代碼庫和(hé)作(zuò)者代碼庫中提取數據并對(duì)它們進行組合:

class GetLatestNewsWithAuthorsUseCase(

private val newsRepository: NewsRepository,

private val authorsRepository: AuthorsRepository

) { /* ... */ }

由于用(yòng)例包含可重複使用(yòng)的邏輯,因此其他(tā)用(yòng)例也(yě)可以使用(yòng)這(zhè)些(xiē)用(yòng)例。在網域層有多個用(yòng)例層級是正常現(xiàn)象。例如,如果界面層中的多個類依賴時(shí)區(qū)在屏幕上(shàng)顯示适當的消息,則以下(xià)示例中定義的用(yòng)例可以使用(yòng) FormatDateUseCase 用(yòng)例:

class GetLatestNewsWithAuthorsUseCase(

private val newsRepository: NewsRepository,

private val authorsRepository: AuthorsRepository,

private val formatDateUseCase: FormatDateUseCase

) { /* ... */ }

調用(yòng) Kotlin 中的用(yòng)例

在 Kotlin 中,您可以通過使用(yòng) operator 修飾符定義 invoke() 函數,将用(yòng)例類實例作(zuò)爲函數進行調用(yòng)。請(qǐng)參閱以下(xià)示例:

class FormatDateUseCase(userRepository: UserRepository) {

private val formatter = SimpleDateFormat(

userRepository.getPreferredDateFormat(),

userRepository.getPreferredLocale()

)

operator fun invoke(date: Date): String {

return formatter.format(date)

}

}

在此示例中,您可以通過 FormatDateUseCase 中的 invoke() 方法将類的實例作(zuò)爲函數一樣調用(yòng)。invoke() 方法不限于任何特定簽名,它可以接受任意數量的參數并返回任何類型。您還可以在類中使用(yòng)不同的簽名使 invoke() 重載。您可以調用(yòng)上(shàng)述示例中的用(yòng)例,如下(xià)所示:

class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {

init {

val today = Calendar.getInstance()

val todaysDate = formatDateUseCase(today)

/* ... */

}

}

如需詳細了(le)解 invoke() 運算(suàn)符,請(qǐng)參閱 Kotlin 文(wén)檔。

生命周期

用(yòng)例沒有自(zì)己的生命周期,而是受限于使用(yòng)它們的類。這(zhè)意味着,您可以從(cóng)界面層中的類、服務或 Application 類本身調用(yòng)用(yòng)例。由于用(yòng)例不應包含可變數據,因此您每次将用(yòng)例類作(zuò)爲依賴項傳遞時(shí),都應該創建一個新實例。

線程處理(lǐ)

來(lái)自(zì)網域層的用(yòng)例必須具有主線程安全性;換句話(huà)說,它們必須能(néng)安全地從(cóng)主線程調用(yòng)。如果用(yòng)例類執行長期運行的阻塞操作(zuò),那麽它們負責将該邏輯移至适當的線程。不過,在執行此操作(zuò)之前,請(qǐng)檢查這(zhè)些(xiē)阻塞操作(zuò)是否最好(hǎo)放(fàng)置在層次結構的其他(tā)層中。通常,數據層中會(huì)進行複雜(zá)的計(jì)算(suàn),以促進可重用(yòng)性或緩存。例如,如果某項結果需要緩存起來(lái),以便在應用(yòng)的多個屏幕上(shàng)重複使用(yòng),那麽在數據層中對(duì)大(dà)列表執行資源密集型操作(zuò)比在網域層中執行會(huì)更好(hǎo)。

以下(xià)示例顯示了(le)一個在後台線程上(shàng)執行工(gōng)作(zuò)的用(yòng)例:

class MyUseCase(

private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default

) {

suspend operator fun invoke(...) = withContext(defaultDispatcher) {

// Long-running blocking operations happen on a background thread.

}

}

常見任務

本部分介紹如何執行常見網域層任務。

可重複使用(yòng)的簡單業務邏輯

您應将界面層中存在的可重複業務邏輯封裝到(dào)用(yòng)例類中。這(zhè)樣您就可以更輕松地在使用(yòng)該邏輯的所有位置應用(yòng)任何更改,還可以單獨測試邏輯。

考慮前面介紹的 FormatDateUseCase 示例。如果将來(lái)關于數據格式的業務要求發生變化,您隻需在一個地方更改代碼。

注意:在某些(xiē)情況下(xià),用(yòng)例中可能(néng)存在的邏輯可以成爲 Util 類中靜态方法的一部分。不過,不建議(yì)采用(yòng)後者,因爲 Util 類通常很(hěn)難找到(dào),而且其功能(néng)也(yě)很(hěn)難發現(xiàn)。此外(wài),用(yòng)例還可以共享通用(yòng)功能(néng)(例如基類中的線程處理(lǐ)和(hé)錯誤處理(lǐ)),這(zhè)對(duì)規模較大(dà)的大(dà)型團隊很(hěn)有助益。

合并代碼庫

在新聞應用(yòng)中,您可能(néng)擁有分别處理(lǐ)新聞和(hé)作(zuò)者數據操作(zuò)的 NewsRepository 和(hé) AuthorsRepository 類。NewsRepository 提供的 Article 類僅包含作(zuò)者的姓名,但(dàn)您希望在屏幕上(shàng)顯示關于作(zuò)者的更多信息。作(zuò)者信息可通過 AuthorsRepository 獲取。

由于該邏輯涉及多個代碼庫并且可能(néng)會(huì)變得很(hěn)複雜(zá),因此您可以創建 GetLatestNewsWithAuthorsUseCase 類,将邏輯從(cóng) ViewModel 中提取出來(lái)并提高(gāo)其可讀性。這(zhè)也(yě)使得邏輯更易于單獨測試,并且可在應用(yòng)的不同部分重複使用(yòng)。

/**

* This use case fetches the latest news and the associated author.

*/

class GetLatestNewsWithAuthorsUseCase(

private val newsRepository: NewsRepository,

private val authorsRepository: AuthorsRepository,

private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default

) {

suspend operator fun invoke(): List =

withContext(defaultDispatcher) {

val news = newsRepository.fetchLatestNews()

val result: MutableList = mutableListOf()

// This is not parallelized, the use case is linearly slow.

for (article in news) {

// The repository exposes suspend functions

val author = authorsRepository.getAuthor(article.authorId)

result.add(ArticleWithAuthor(article, author))

}

result

}

}

該邏輯會(huì)映射 news 列表中的所有項;因此,即使數據層是主線程安全的,此工(gōng)作(zuò)不應該阻止主線程,因爲您并不知(zhī)道(dào)它會(huì)處理(lǐ)多少項。正因如此,該用(yòng)例使用(yòng)默認調度程序将工(gōng)作(zuò)移到(dào)後台線程。

注意:借助 Room 庫,您可以查詢數據庫中不同實體之間的關系。如果數據庫是可信來(lái)源,您可以創建一個查詢,讓系統爲您執行所有操作(zuò)。在這(zhè)種情況下(xià),最好(hǎo)創建代碼庫類(例如 NewsWithAuthorsRepository),而不是用(yòng)例。

其他(tā)使用(yòng)方

除了(le)界面層之外(wài),網域層還可以被其他(tā)類(如服務和(hé) Application 類)重複使用(yòng)。此外(wài),如果其他(tā)平台(如 TV 或 Wear)與移動應用(yòng)共享代碼庫,則它們的界面層還可以重複使用(yòng)用(yòng)例,以取得網域層的所有上(shàng)述優勢。

數據層訪問權限限制

實現(xiàn)網域層時(shí),還需要考慮應該仍然允許從(cóng)界面層直接訪問數據層,還是應該強制要求所有訪問都必須通過網域層進行。

設置此限制的好(hǎo)處之一是,這(zhè)會(huì)阻止界面繞過網域層邏輯,例如,當您對(duì)針對(duì)數據層的每個訪問請(qǐng)求執行分析日志記錄時(shí)。

不過,潛在的重要缺陷在于,您不得不添加相關用(yòng)例,即使隻是對(duì)數據層進行簡單的函數調用(yòng)時(shí)也(yě)是如此,而這(zhè)可能(néng)會(huì)增加複雜(zá)性,卻幾乎沒有什(shén)麽好(hǎo)處。

一種很(hěn)好(hǎo)的方法是僅在需要時(shí)才添加用(yòng)例。如果您發現(xiàn)界面層幾乎完全通過用(yòng)例訪問數據,那麽僅以這(zhè)種方式訪問數據可能(néng)是合理(lǐ)的。

最終,是否限制對(duì)數據層的訪問權限取決于您的具體代碼庫,以及您傾向于采用(yòng)更嚴格的規則還是更靈活的方法。

測試

測試網域層時(shí),請(qǐng)遵循通用(yòng)測試指南。對(duì)于其他(tā)界面測試,開(kāi)發者通常使用(yòng)虛構代碼庫,因此在測試網域層時(shí),最好(hǎo)使用(yòng)虛構代碼庫。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:Android數據層
上(shàng)一篇:Android界面狀态生成