網域層是使用者介面層和資料層之間的選用層。

網域層負責封裝複雜的商業邏輯,或是多個 ViewModel 重複使用的簡易商業邏輯。此層為選用性質 ,因為並非所有應用程式都會有上述要求。建議只在有需要時才使用, 例如處理複雜程度或支持重複使用時。
網域層具備下列優點:
- 避免程式碼重複的情形。
- 提高使用網域層類別的類別可讀性。
- 藉此提升應用程式的測試能力。
- 可透過分擔責任以避免大型類別。
為保持這些類別的簡單與輕量化,每個用途應只負責處理單一功能,同時它們不應該包含可變動資料。相反,您應該在使用者介面或是資料層處理可變動資料。
本指南中的命名慣例
在本指南中,用途是以使用者負責的單一動作命名。慣例如下:
現在時態中的動詞 + noun/what (選用) + UseCase。
例如:FormatDateUseCase
、LogOutUserUseCase
、
GetLatestNewsWithAuthorsUseCase
或 MakeLoginRequestUseCase
。
依附元件
在典型的應用程式架構中,適用使用者介面層的 ViewModels 與資料層的存放區之間的用途類別。這表示用途類別 通常取決於存放區類別,而且這些類別與存放區的通訊方式 會與存放區相同,使用回呼 (Java) 或處理常式 (Kotlin) 的方式。若要瞭解詳情,請參閱資料層 頁面。
舉例來說,您的應用程式可能有一個用途類別,可從新聞存放區和作者存放區擷取資料,並且將它們結合:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository
) { /* ... */ }
由於用途包含可重複使用的邏輯,因此其他用途也可使用這些用途。網域層有多個用途等級是正常的情況。舉
例來說,如果使用者介面層的多個類別依賴時區,
以便在螢幕上顯示適當的訊息,下面範例定義的用途可以利用 FormatDateUseCase
用途:
class GetLatestNewsWithAuthorsUseCase(
private val newsRepository: NewsRepository,
private val authorsRepository: AuthorsRepository,
private val formatDateUseCase: FormatDateUseCase
) { /* ... */ }

Kotlin 中的呼叫用途
在 Kotlin 中,您可以使用 operator
輔助鍵定義 invoke()
函式,即可將用途類別執行個體呼叫為函式。請參閱以下
範例:
class FormatDateUseCase(userRepository: UserRepository) {
private val formatter = SimpleDateFormat(
userRepository.getPreferredDateFormat(),
userRepository.getPreferredLocale()
)
operator fun invoke(date: Date): String {
return formatter.format(date)
}
}
在此範例中,FormatDateUseCase
中的 invoke()
方法可讓您
呼叫該類別的執行個體,就如同它們是函式一樣。invoke()
方法
不限於任何特定簽名,可以使用任意數量的參數
並傳回任何類型。您也可以在您的類別中使用不同的簽名,讓 invoke()
超載。您可以按如下方法呼叫上方範例中的用途:
class MyViewModel(formatDateUseCase: FormatDateUseCase) : ViewModel() {
init {
val today = Calendar.getInstance()
val todaysDate = formatDateUseCase(today)
/* ... */
}
}
如要進一步瞭解 invoke()
運算子,請參閱 Kotlin 文件。
生命週期
用途本身沒有生命週期。相反,它們的範圍限定於使用該用途的類別。這表示您可以透過使用者介面層中的類別、透過服務或透過 Application
類別本身呼叫用途。由於用途
不應包含可變動的資料,因此每次您將其作為依附元件傳送時,
都應該建立新的用途類別執行個體。
執行緒
網域層的用途必須是 main-safe;換句話說,它們必須能夠安全地從主執行緒呼叫。如果用途類別執行長時間的封鎖作業,則必須負責將該邏輯移至適當的執行緒。不過,在進行轉移作業前,請先檢查這些封鎖作業是否放置在階層的其他層進行會更好。一般來說, 資料層會進行複雜運算,藉此鼓勵重複使用 或快取。舉例來說,如果需要快取結果以便在應用程式的 多個螢幕上重複使用,將大型清單中資源密集的作業放置 在資料層會比放置在網域層來得好。
以下範例顯示在背景執行緒上執行作業的用途:
class MyUseCase(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend operator fun invoke(...) = withContext(defaultDispatcher) {
// Long-running blocking operations happen on a background thread.
}
}
一般工作
本節說明如何執行常見的網域層工作。
可重複使用的簡單商業邏輯
您應該將用途類別使用者介面層中的重複商業邏輯 封裝在一起。如此一來,在使用邏輯的所有位置都能更輕鬆地套用變更。您還可以在隔離狀態測試邏輯。
請參照前述的 FormatDateUseCase
範例。如果貴企業
日後在有關日期格式要求方面會有所變動,只需要在一個集中的位置
變更程式碼即可。
合併存放區
在新聞應用程式中,可將 NewsRepository
和 AuthorsRepository
類別分別設定為處理新聞和作者資料作業。NewsRepository
顯示的
Article
類別只包含作者的姓名,但您想要
在螢幕上顯示作者的更多相關資訊。您可以
從 AuthorsRepository
取得作者資訊。

由於邏輯涉及多個存放區,且可能變得較為複雜,因此您可以建立 GetLatestNewsWithAuthorsUseCase
類別將邏輯從 ViewModel 中摘錄出來,使其更具可讀性。這也使得邏輯更容易在隔離狀態下測試,
進而在應用程式的不同部分中重複使用。
/**
* 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<ArticleWithAuthor> =
withContext(defaultDispatcher) {
val news = newsRepository.fetchLatestNews()
val result: MutableList<ArticleWithAuthor> = 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
清單中的所有項目;因此,即使資料層是安全的,但您不知道它會處理的項目會有多少,因此這項作業不應該封鎖主要執行緒。因此,用途會使用預設調度員將作業移至背景執行緒。
其他消費者
除了使用者介面層以外,網域層可供其他類別 (例如服務和 Application
類別) 重複使用。此外,如果平台 (例如電視或 Wear) 與行動應用程式共用程式碼集,它們的使用者介面層也能重複使用用途,藉此獲得網域層的所有上述優點。
測試
一般測試指南會在測試網域層時套用。對於其他使用者介面測試,開發人員通常會使用假存放區,因此最好在測試網域層時也使用假存放區。