上一天我們提到了 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 之類的),又會覺得沒有這個必要,所以我覺得目前這樣放是一個比較好的選項。
還有 CoEditor 與 ContextMenu這兩個元件之間的關係是組合,CoEditor 擁有 ContextMenu ,但是 ContextMenu 並不是永遠都是處於可見的狀態,所以我另外新增了兩個狀態:showContextMenu, showAdderButton。
class CoEditor {
val showContextMenu: Observable<Boolean>
val showAdderButton: Observable<Boolean>
...
val contextMenu = ContextMenu()
}
最後是 CoEditor 與 NoteRepository 之間的關係,理所當然的,也是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 被好好的回收掉。