iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Mobile Development

Jetpack Compose X Android Architecture X Functional Reactive Programming系列 第 26

Re-architect - Domain Layer (一)

上一天我們提到了 Domain Layer 會包含以下三個組件:CoEditor, ContextMenu, NoteRepository ,今天我們將著重在介面設計。


這個“共編器”將會提供所有的核心操作邏輯,基本的公開函式與變數如下:

class CoEditor {
	  val allVisibleNotes: Observable<List<String>>
		val selectedNote: Observable<Optional<Note>>

		fun selectNote(noteId: String) 
		fun clearSelection()
		fun addNewNote()
		fun moveNote(noteId: String, positionDelta: Position)
}

我們可以發現其實很多函式的介面跟之前的 EditorViewModel 一模一樣,因為 EditorViewModel 在原本的架構中就是屬於 Domain 層的元件,這樣的結果一點都不奇怪。至於其他原本 EditorViewModel 有的公開函式但是必較偏向於選單操作的,我們將它搬移到了 ContextMenu ,介面如下:

class ContextMenu {
    val colorOptions: List<YBColor> 
    val selectedColor: Observable<YBColor> 
    
    fun onColorSelected(color: YBColor)
    fun onDeleteClicked() 
    fun onEditTextClicked()
}

如此一來我們就把原本屬於 EditorViewModel 的職責一分為二了!onColorSelected, onDeleteClicked, onEditTextClicked 這些函式放在 ContextMenu 中也讓第一眼看到這個類別的人,完全知道這類別可以做哪些事情,同時還有公開變數表示了所有顏色的選項 colorOptions 以及現在正在選的顏色 selectedColor ,這兩個變數是給顏色選單顯示用的變數,其實這樣設計並沒有很 “Domain”,這些是比較零散的知識,但是如果要為它再設計另一個階層(像是 ColorMenu 之類的),又會覺得沒有這個必要,所以我覺得目前這樣放是一個比較好的選項。

還有 CoEditorContextMenu這兩個元件之間的關係是組合,CoEditor 擁有 ContextMenu ,但是 ContextMenu 並不是永遠都是處於可見的狀態,所以我另外新增了兩個狀態:showContextMenu, showAdderButton。

class CoEditor {
    val showContextMenu: Observable<Boolean> 
    val showAdderButton: Observable<Boolean> 
    ...
    val contextMenu = ContextMenu()
}

最後是 CoEditorNoteRepository 之間的關係,理所當然的,也是CoEditor 擁有 NoteRepository ,不然 CoEditor 將無法拿到所有便利貼的狀態:

class CoEditor(private val noteRepository: NoteRepository)

NoteRepository 的實作將會是 Firebase 並且由 Dependency Injection framework 來提供。CoEditor最後完整的公開函式會是像這樣:

class CoEditor(private val noteRepository: NoteRepository) {
	  val allVisibleNotes: Observable<List<String>>
		val selectedNote: Observable<Optional<Note>>
    val showContextMenu: Observable<Boolean> 
    val showAdderButton: Observable<Boolean> 
    val openEditTextScreen: Observable<String> // 打開編輯文字頁面,上面沒提到

    val contextMenu = ContextMenu()

		fun selectNote(noteId: String) 
		fun clearSelection()
		fun addNewNote()
		fun moveNote(noteId: String, positionDelta: Position)
}

class ContextMenu {
    val colorOptions: List<YBColor> 
    val selectedColor: Observable<YBColor> 
    
    fun onColorSelected(color: YBColor)
    fun onDeleteClicked() 
    fun onEditTextClicked()
}

但這邊還有一個問題,RxJava 一但綁定了,就必須要處理他的生命週期,CoEditor 一定也免不了這件事情發生,這也代表了這個類別中會有一些非同步的任務,所有的非同步任務都要好好的被處理,不然會有 memory leak 的問題發生,於是這個 CoEditor 也會是一個有生命週期的元件,其生命週期的事件將由外界控制:

class CoEditor(private val noteRepository: NoteRepository) {
    ....
    
    private val disposableBag = CompositeDisposable() 

    fun start() { ... }
    fun stop() { disposableBag.clear() }
}

通常來說一個具有生命週期的元件都是要有成對的生命週期事件,有死亡(stop)就要有出生(start),所有在 start 中被觀察的 Observable 要在 stop 被好好的回收掉。


上一篇
使用 Domain Driven Design 來進行架構設計
下一篇
Re-architect - Domain Layer (二)
系列文
Jetpack Compose X Android Architecture X Functional Reactive Programming30

尚未有邦友留言

立即登入留言