做(zuò)自(zì)由與創造的先行(xíng)者

Android網域層

Android開(kāi)發手冊

網域層是位于界面層和(hé)數(shù)據層之間(jiān)的可(kě)選層。

網域層負責封裝複雜(zá)的業務邏輯,或者由多個 ViewModel 重複使用的簡單業務邏輯。此層是可(kě)選的,因為(wèi)并非所有應用都(dōu)有這(zhè)類需求。因此,您應僅在需要(yào)時(shí)使用該層,例如(rú)處理複雜(zá)邏輯或支持可(kě)重用性。

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

避免代碼重複。

改善使用網域層類的類的可(kě)讀性。

改善應用的可(kě)測試性。

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

為(wèi)了(le)使這(zhè)些類保持簡單輕量化,每個用例都(dōu)應僅負責單個功能(néng),且不應包含可(kě)變數(shù)據。您應在界面或數(shù)據層中處理可(kě)變的數(shù)據。

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

本指南中的命名慣例

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

一(yī)般現在時(shí)動詞 + 名詞/內(nèi)容(可(kě)選)+ 用例。

例如(rú):FormatDateUseCase、LogOutUserUseCase、GetLatestNewsWithAuthorsUseCase 或 MakeLoginRequestUseCase。

依賴關系

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

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

class GetLatestNewsWithAuthorsUseCase(

private val newsRepository: NewsRepository,

private val authorsRepository: AuthorsRepository

) { /* ... */ }

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

class GetLatestNewsWithAuthorsUseCase(

private val newsRepository: NewsRepository,

private val authorsRepository: AuthorsRepository,

private val formatDateUseCase: FormatDateUseCase

) { /* ... */ }

調用 Kotlin 中的用例

在 Kotlin 中,您可(kě)以通(tōng)過使用 operator 修飾符定義 invoke() 函數(shù),将用例類實例作(zuò)為(wèi)函數(shù)進行(xí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)

}

}

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

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

init {

val today = Calendar.getInstance()

val todaysDate = formatDateUseCase(today)

/* ... */

}

}

如(rú)需詳細了(le)解 invoke() 運算符,請(qǐng)參閱 Kotlin 文檔。

生命周期

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

線程處理

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

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

class MyUseCase(

private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default

) {

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

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

}

}

常見(jiàn)任務

本部分介紹如(rú)何執行(xíng)常見(jiàn)網域層任務。

可(kě)重複使用的簡單業務邏輯

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

考慮前面介紹的 FormatDateUseCase 示例。如(rú)果将來關于數(shù)據格式的業務要(yào)求發生變化,您隻需在一(yī)個地(dì)方更改代碼。

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

合并代碼庫

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

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

/**

* 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

}

}

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

注意:借助 Room 庫,您可(kě)以查詢數(shù)據庫中不同實體之間(jiān)的關系。如(rú)果數(shù)據庫是可(kě)信來源,您可(kě)以創建一(yī)個查詢,讓系統為(wèi)您執行(xíng)所有操作(zuò)。在這(zhè)種情況下(xià),最好創建代碼庫類(例如(rú) NewsWithAuthorsRepository),而不是用例。

其他(tā)使用方

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

數(shù)據層訪問(wèn)權限限制

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

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

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

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

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

測試

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

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